이메일 전송
UTL_SMTP와 UTL_MAIL을 이용해 이메일을 보내는 방법을 배웠는데 이전 장에서는 익명 블록으로 메일을 보냈지만, 이번에는 보내는 사람, 받는 사람 주소, 제목, 메일 내용, 첨부파일 등을 매개변수로 입력받아 이메일을 보내는 email_send_prc 라는 프로시저를 만들어 보자. 지금까지 만든 다른 유틸리티 프로그램과는 달리 이 프로시저에서는 패키지 변수를 선언해 사용할 것이다. SMTP 서버 주소명, 포트번호, 도메인, boundary 문자열이 그 대상이다.
먼저 패키지 선언부에서 관련 프로시저와 패키지 상수를 선언해 보자.
입력
CREATE OR REPLACE PACKAGE BODY my_util_pkg IS
...
...
-- 이메일 전송과 관련된 패키지 상수
pv_host VARCHAR2(10) := 'localhost'; -- SMTP 서버명
pn_port NUMBER := 25; -- 포트번호
pv_domain VARCHAR2(30) := 'hong.com'; -- 도메인명
pv_boundary VARCHAR2(50) := 'DIFOJSLKDWFEFO.WEFOWJFOWE'; -- boundary text
pv_directory VARCHAR2(50) := 'SMTP_FILE'; --파일이 있는 디렉토리명
-- 5. 이메일 전송
PROCEDURE email_send_prc ( ps_from IN VARCHAR2,
ps_to IN VARCHAR2,
ps_subject IN VARCHAR2,
ps_body IN VARCHAR2,
ps_content IN VARCHAR2 DEFAULT 'text/plain;',
ps_file_nm IN VARCHAR2
);
END my_util_pkg;
결과
PACKAGE MY_UTIL_PKG이(가) 컴파일되었습니다.
이제 패키지 본문, 즉 email_send_prc 프로시저의 본문을 작성해야 하는데 기본적인 내용은 이전 장에서 살펴봤던 예제와 크게 다르지 않은데 이 익명 블록 예제는 한 가지 문제점이 있다. 바로 크기가 32kb 이하의 파일만 첨부해 보낼 수 있다는 점이다.
이전 장의 익명 블록을 사용해 32KB 이상 파일을 첨부하면 오류가 발생하는데, 그 원인은 UTL_SMTP 패키지의 문제가 아니라 바로 파일 이름을 입력받아 해당 파일을 RAW 타입으로 반환하는 fn_get_raw_file 함수의 문제다. 이 함수의 소스를 보면 내부적으로 UTL_FILE.GET_RAW 프로시저를 사용해서 해당 파일을 vf_raw라는 변수에 넣어 이를 반환하는데, vf_raw는 RAW 타입으로 최댓값이 32767, 즉 32KB이다. 따라서 32KB 이상의 파일을 읽어 반환할 수 없는 것이다.
따라서 32KB보다 크기가 큰 파일을 처리하려면 RAW 타입이 아닌 LOB 타입을 사용해야 한다. email_send_prc 프로시저에서는 파일 처리를 LOB 타입 중 하나인 BFILE 타입으로 받아 처리할 것이다. 그럼 프로시저를 작성해 보자.
입력
CREATE OR REPLACE PACKAGE BODY my_util_pkg IS
...
...
PROCEDURE email_send_prc ( ps_from IN VARCHAR2, -- 보내는 사람
ps_to IN VARCHAR2, -- 받는 사람
ps_subject IN VARCHAR2, -- 제목
ps_body IN VARCHAR2, -- 본문
ps_content IN VARCHAR2 DEFAULT 'text/plain;',
ps_file_nm IN VARCHAR2 -- 첨부파일
)
IS
vc_con utl_smtp.connection;
v_bfile BFILE; -- 파일을 담을 변수
vn_bfile_size NUMBER := 0; -- 파일크기
v_temp_blob BLOB := EMPTY_BLOB; -- 파일을 옮겨담을 BLOB 타입 변수
vn_blob_size NUMBER := 0; -- BLOB 변수 크기
vn_amount NUMBER := 54; -- 54 단위로 파일을 잘라 메일에 붙이기 위함
v_tmp_raw RAW(54); -- 54 단위로 자른 파일내용이 담긴 RAW 타입변수
vn_pos NUMBER := 1; --파일 위치를 담는 변수
BEGIN
vc_con := UTL_SMTP.OPEN_CONNECTION(pv_host, pn_port);
UTL_SMTP.HELO(vc_con, pv_domain); -- HELO
UTL_SMTP.MAIL(vc_con, ps_from); -- 보내는사람
UTL_SMTP.RCPT(vc_con, ps_to); -- 받는사람
UTL_SMTP.OPEN_DATA(vc_con); -- 메일본문 작성 시작
UTL_SMTP.WRITE_DATA(vc_con,'MIME-Version: 1.0' || UTL_TCP.CRLF ); -- MIME 버전
UTL_SMTP.WRITE_DATA(vc_con,'Content-Type: multipart/mixed; boundary="' || pv_boundary || '"' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_RAW_DATA(vc_con, UTL_RAW.CAST_TO_RAW('From: ' || ps_from || UTL_TCP.CRLF) );
UTL_SMTP.WRITE_RAW_DATA(vc_con, UTL_RAW.CAST_TO_RAW('To: ' || ps_to || UTL_TCP.CRLF) );
UTL_SMTP.WRITE_RAW_DATA(vc_con, UTL_RAW.CAST_TO_RAW('Subject: ' || ps_subject || UTL_TCP.CRLF) );
UTL_SMTP.WRITE_DATA(vc_con, UTL_TCP.CRLF );
-- 메일 본문
UTL_SMTP.WRITE_DATA(vc_con, '--' || pv_boundary || UTL_TCP.CRLF );
UTL_SMTP.WRITE_DATA(vc_con, 'Content-Type: ' || ps_content || UTL_TCP.CRLF );
UTL_SMTP.WRITE_DATA(vc_con, 'charset=euc-kr' || UTL_TCP.CRLF );
UTL_SMTP.WRITE_DATA(vc_con, UTL_TCP.CRLF );
UTL_SMTP.WRITE_RAW_DATA(vc_con, UTL_RAW.CAST_TO_RAW(ps_body || UTL_TCP.CRLF) );
UTL_SMTP.WRITE_DATA(vc_con, UTL_TCP.CRLF );
-- 첨부파일이 있다면 ...
IF ps_file_nm IS NOT NULL THEN
UTL_SMTP.WRITE_DATA(vc_con, '--' || pv_boundary || UTL_TCP.CRLF );
-- 파일의 Content-Type은 application/octet-stream
UTL_SMTP.WRITE_DATA(vc_con,'Content-Type: application/octet-stream; name="' || ps_file_nm || '"' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(vc_con,'Content-Transfer-Encoding: base64' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(vc_con,'Content-Disposition: attachment; filename="' || ps_file_nm || '"' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(vc_con, UTL_TCP.CRLF);
-- 파일처리 시작
-- 파일을 읽어 BFILE 변수인 v_bfile에 담는다.
v_bfile := BFILENAME(pv_directory, ps_file_nm);
-- v_bfile 담은 파일을 읽기전용으로 연다.
DBMS_LOB.OPEN(v_bfile, DBMS_LOB.LOB_READONLY);
-- v_bfile에 담긴 파일의 크기를 가져온다.
vn_bfile_size := DBMS_LOB.GETLENGTH(v_bfile);
-- v_bfile를 BLOB 변수인 v_temp_blob에 담기 위해 초기화
DBMS_LOB.CREATETEMPORARY(v_temp_blob, TRUE);
-- v_bfile에 담긴 파일을 v_temp_blob 로 옮긴다.
DBMS_LOB.LOADFROMFILE(v_temp_blob, v_bfile, vn_bfile_size);
-- v_temp_blob의 크기를 구한다.
vn_blob_size := DBMS_LOB.GETLENGTH(v_temp_blob);
-- vn_pos 초기값은 1, v_temp_blob 크기보다 작은 경우 루프
WHILE vn_pos < vn_blob_size
LOOP
-- v_temp_blob에 담긴 파일을 vn_amount(54)씩 잘라 v_tmp_raw에 담는다.
DBMS_LOB.READ(v_temp_blob, vn_amount, vn_pos, v_tmp_raw);
-- 잘라낸 v_tmp_raw를 메일에 첨부한다.
UTL_SMTP.WRITE_RAW_DATA(vc_con, UTL_ENCODE.BASE64_ENCODE ( v_tmp_raw));
UTL_SMTP.WRITE_DATA(vc_con, UTL_TCP.CRLF );
v_tmp_raw := NULL;
vn_pos := vn_pos + vn_amount;
END LOOP;
DBMS_LOB.FREETEMPORARY(v_temp_blob); -- v_temp_blob 메모리 해제
DBMS_LOB.FILECLOSE(v_bfile); -- v_bfile 닫기
END IF; -- 첨부파일 처리 종료
-- 맨 마지막 boundary에는 앞과 뒤에 '--'를 반드시 붙여야 한다.
UTL_SMTP.WRITE_DATA(vc_con, '--' || pv_boundary || '--' || UTL_TCP.CRLF );
UTL_SMTP.CLOSE_DATA(vc_con); -- 메일 본문 작성 종료
UTL_SMTP.QUIT(vc_con); -- 메일 세션 종료
EXCEPTION
WHEN UTL_SMTP.INVALID_OPERATION THEN
dbms_output.put_line(' Invalid Operation in Mail attempt using UTL_SMTP.');
dbms_output.put_line(sqlerrm);
UTL_SMTP.QUIT(vc_con);
WHEN UTL_SMTP.TRANSIENT_ERROR THEN
dbms_output.put_line(' Temporary e-mail issue - try again');
UTL_SMTP.QUIT(vc_con);
WHEN UTL_SMTP.PERMANENT_ERROR THEN
dbms_output.put_line(' Permanent Error Encountered.');
dbms_output.put_line(sqlerrm);
UTL_SMTP.QUIT(vc_con);
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
UTL_SMTP.QUIT(vc_con);
END email_send_prc;
...
...
결과
PACKAGE BODY MY_UTIL_PKG이(가) 컴파일되었습니다.
이제 HTML 형태의 첨부파일이 포함된 메일을 보내 보자. 이번에 첨부할 파일은 그 크기가 32kb 이상인 “seoul.xls”파일을 전송해 볼 텐데 그 전에 해당 파일을 “C:\ch18_file” 디렉토리에 넣은 다음 프로시저를 호출해 보자.
입력
DECLARE
vv_html VARCHAR2(1000);
BEGIN
vv_html := '<HTML> <HEAD>
<TITLE>HTML 테스트</TITLE>
</HEAD>
<BDOY>
<p>이 메일은 <b>HTML</b> <i>버전</i> 으로 </p>
<p> <strong>my_util_pkg</strong> 패키지의 email_send_prc 프로시저를 사용해 보낸 메일입니다. </p>
</BODY>
</HTML>';
-- 이메일전송
my_util_pkg.email_send_prc ( ps_from => 'charieh@hong.com'
,ps_to => 'charieh@hong.com'
,ps_subject => '테스트 메일'
,ps_body => vv_html
,ps_content => 'text/html;'
,ps_file_nm => 'hong1.txt'
);
END;
결과
익명 블록이 완료되었습니다.
제대로 전송됐는지 확인해 보자.
첨부파일을 열어 보자.
첨부파일까지 제대로 전송된 것을 알 수 있다. 이 프로시저의 소스가 약간 복잡하게 보이지만, 첨부파일을 보내는 루틴을 별도의 프로시저로 분리해 낸다면 훨씬 가독성이 높아질 것이다.