Oracle 內建 UTL_SMTP 套件可直接在 PL/SQL 中發送郵件,搭配 UTL_ENCODE 處理 Base64 認證與 UTL_RAW 處理編碼轉換,即可實作支援中文主旨/內容、SMTP AUTH 驗證的郵件發送功能。
create or replace procedure send_mail_(
p_from varchar2, -- 寄件人信箱
p_fromuser varchar2, -- 寄件人顯示名稱
p_touser varchar2, -- 收件人顯示名稱
p_to varchar2, -- 收件人信箱(多筆用 ; 分隔)
p_cc varchar2, -- 副本信箱
p_subject varchar2, -- 主旨
p_message varchar2, -- 內容(支援 HTML)
p_user varchar2, -- SMTP 帳號
p_mailhost varchar2, -- SMTP 主機
p_psd varchar2 -- SMTP 密碼
) is
v_conn utl_smtp.connection;
v_msg varchar2(32700);
v_touser1 varchar2(4000);
v_to varchar2(500);
v_touser varchar2(500);
psrc varchar2(4000);
psrc1 varchar2(4000);
i number := 1; j number := 1; m number := 1; n number := 1;
str_error varchar2(20000);
begin
-- 1. 建立連線
v_conn := utl_smtp.open_connection(p_mailhost, 25);
utl_smtp.ehlo(v_conn, p_mailhost); -- 必須用 EHLO 支援 AUTH
-- 2. SMTP AUTH LOGIN
utl_smtp.command(v_conn, 'AUTH LOGIN');
utl_smtp.command(v_conn, utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw(p_user))));
utl_smtp.command(v_conn, utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw(p_psd))));
-- 3. 寄件人
utl_smtp.mail(v_conn, '<' || p_from || '>');
-- 4. 收件人(支援多筆 ; 分隔)
psrc := rtrim(ltrim(p_to, ';'), ';') || ';';
psrc1 := rtrim(ltrim(p_touser, ';'), ';') || ';';
if nvl(instr(psrc, ';', j), 0) > 0 then
loop
i := nvl(instr(psrc, ';', j), 0);
m := nvl(instr(psrc1, ';', n), 0);
if i > 0 then
v_to := trim(substr(psrc, j, i - j));
v_touser := nvl(trim(substr(psrc1, n, m - n)), '1');
utl_smtp.rcpt(v_conn, v_to);
if v_touser = '1' then
v_touser := trim(substr(v_to, 1, instr(v_to, '@') - 1));
end if;
if j = 1 then
v_touser1 := v_touser1 || v_touser || '<' || v_to || '>';
else
v_touser1 := v_touser1 || ';' || v_touser || '<' || v_to || '>';
end if;
j := i + 1; n := m + 1;
end if;
exit when i = 0;
end loop;
else
utl_smtp.rcpt(v_conn, p_to);
v_touser1 := v_touser1 || p_touser || '<' || p_to || '>';
end if;
-- 5. 副本(CC)
i := 1; j := 1;
if nvl(p_cc, 'aa') <> 'aa' then
psrc := rtrim(ltrim(p_cc, ';'), ';') || ';';
if nvl(instr(psrc, ';', j), 0) > 0 then
loop
i := nvl(instr(psrc, ';', j), 0);
if i > 0 then
v_to := trim(substr(psrc, j, i - j));
j := i + 1;
utl_smtp.rcpt(v_conn, v_to);
end if;
exit when i = 0;
end loop;
elsif nvl(p_cc, '1') <> '1' then
utl_smtp.rcpt(v_conn, p_cc);
end if;
end if;
-- 6. 組裝郵件內容(Header + Body)
v_msg := 'Date: ' || to_char(sysdate, 'dd mon yy hh24:mi:ss') || utl_tcp.crlf ||
'From: ' || p_fromuser || '<' || p_from || '>' || utl_tcp.crlf ||
'To: ' || v_touser1 || utl_tcp.crlf ||
'Cc: ' || p_cc || utl_tcp.crlf ||
'Subject: ' || p_subject || utl_tcp.crlf ||
utl_tcp.crlf || -- 空行分隔 Header/Body
p_message;
-- 7. 發送資料(指定 UTF-8 避免中文亂碼)
utl_smtp.open_data(v_conn);
utl_smtp.write_raw_data(v_conn,
utl_raw.cast_to_raw(convert('Content-Type: text/html; charset=utf-8' || v_msg, 'ZHS16GBK')));
utl_smtp.close_data(v_conn);
utl_smtp.quit(v_conn);
exception
when others then
str_error := dbms_utility.format_error_stack || dbms_utility.format_call_stack;
dbms_output.put_line(str_error);
-- 實務建議寫入錯誤日誌表
end;
/
begin
send_mail_(
p_from => 'noreply@example.com',
p_fromuser => '系統通知',
p_touser => '張三;李四',
p_to => 'user1@example.com;user2@example.com',
p_cc => 'manager@example.com',
p_subject => '測試郵件 - ' || to_char(sysdate, 'YYYY-MM-DD HH24:MI'),
p_message => 'HTML 內容測試
支援中文與 粗體。
',
p_user => 'smtp_user',
p_mailhost => 'smtp.example.com',
p_psd => 'smtp_password'
);
end;
/
| 技術 | 用途 |
|---|---|
UTL_SMTP.EHLO | 啟用 ESMTP,支援 AUTH、STARTTLS 等擴展指令 |
AUTH LOGIN | Base64 編碼帳號/密碼進行 SMTP 認證 |
UTL_ENCODE.BASE64_ENCODE | 將帳號密碼轉為 Base64 字串 |
UTL_RAW.CAST_TO_RAW/VARCHAR2 | RAW ↔ VARCHAR2 互轉,處理二進位資料 |
CONVERT(..., 'ZHS16GBK') | 將 UTF-8 字串轉為資料庫字元集再傳送 |
Content-Type: text/html; charset=utf-8 | 宣告郵件為 HTML 格式且 UTF-8 編碼 |
UTL_SMTP.STARTTLS,或使用 Oracle Wallet 搭配 UTL_HTTP 呼叫外部 APIAPEX_MAIL (APEX 環境) 或 Java Stored ProcedureDBMS_SCHEDULER 或 Advanced Queuing (AQ) 避免阻塞主交易