Oracle 10g+ 提供 UTL_MAIL 簡化郵件發送,但 UTL_SMTP 兼容性更廣 (8i+)、可控性更強,且支援自定義 MIME 結構 (HTML、附件、內嵌圖片)。本文提供生產級 SENDMAIL 套件完整碼,涵蓋文本/HTML 郵件、錯誤處理、擴展藍圖。
UTL_SMTP、UTL_TCP 已安裝 (SYS schema 下):
SELECT object_name, status FROM dba_objects
WHERE object_name IN ('UTL_SMTP','UTL_TCP') AND owner = 'SYS';
缺失時執行 $ORACLE_HOME/rdbms/admin/utlsmtp.sql、utltcp.sql
CREATE OR REPLACE PACKAGE sendmail IS
PROCEDURE send (
p_sender VARCHAR2,
p_recipient VARCHAR2,
p_subject VARCHAR2,
p_body VARCHAR2 DEFAULT NULL
);
END sendmail;
/
CREATE OR REPLACE PACKAGE BODY sendmail IS
-- 私有核心流程:建立連線、SMTP 握手、寄件者/收件者、Data 起始
PROCEDURE common (
p_sender VARCHAR2,
p_recipient VARCHAR2,
p_subject VARCHAR2,
c OUT UTL_SMTP.CONNECTION
) IS
v_recipient VARCHAR2(1000) := p_recipient;
BEGIN
-- 1. 連線 SMTP (請替換為實際主機)
c := UTL_SMTP.OPEN_CONNECTION('smtp.yourcompany.com', 25);
-- 2. HELO/EHLO
UTL_SMTP.HELO(c, 'yourcompany.com');
-- 3. MAIL FROM
UTL_SMTP.MAIL(c, p_sender);
-- 4. RCPT TO (支援多收件人,以逗號分隔)
FOR r IN (SELECT TRIM(column_value) addr
FROM TABLE(CAST(STR2TBL(v_recipient) AS TBL_VARCHAR2)))
LOOP
UTL_SMTP.RCPT(c, r.addr);
END LOOP;
-- 5. 開始 DATA 區塊
UTL_SMTP.OPEN_DATA(c);
-- 6. 標準 Header
UTL_SMTP.WRITE_DATA(c, 'From: ' || p_sender || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'To: ' || p_recipient || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'Subject: ' || p_subject || UTL_TCP.CRLF);
EXCEPTION
WHEN UTL_SMTP.TRANSIENT_ERROR OR UTL_SMTP.PERMANENT_ERROR THEN
UTL_SMTP.QUIT(c);
RAISE;
WHEN OTHERS THEN
RAISE;
END common;
-- 公開介面:發送 HTML 郵件
PROCEDURE send (
p_sender VARCHAR2,
p_recipient VARCHAR2,
p_subject VARCHAR2,
p_body VARCHAR2 DEFAULT NULL
) IS
c UTL_SMTP.CONNECTION;
BEGIN
common(p_sender, p_recipient, p_subject, c);
-- Content-Type: text/html 允許 HTML 標籤
UTL_SMTP.WRITE_DATA(c, 'Content-Type: text/html; charset=utf-8' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF || p_body);
UTL_SMTP.CLOSE_DATA(c);
UTL_SMTP.QUIT(c);
EXCEPTION
WHEN UTL_SMTP.TRANSIENT_ERROR OR UTL_SMTP.PERMANENT_ERROR THEN
UTL_SMTP.QUIT(c);
RAISE;
WHEN OTHERS THEN
RAISE;
END send;
END sendmail;
/
CREATE OR REPLACE TYPE tbl_varchar2 AS TABLE OF VARCHAR2(4000);
/
CREATE OR REPLACE FUNCTION str2tbl(p_str VARCHAR2, p_delim VARCHAR2 DEFAULT ',')
RETURN tbl_varchar2 PIPELINED IS
v_idx PLS_INTEGER := 1;
v_len PLS_INTEGER;
BEGIN
LOOP
v_len := INSTR(p_str, p_delim, v_idx);
IF v_len = 0 THEN
PIPE ROW(TRIM(SUBSTR(p_str, v_idx)));
EXIT;
ELSE
PIPE ROW(TRIM(SUBSTR(p_str, v_idx, v_len - v_idx)));
v_idx := v_len + LENGTH(p_delim);
END IF;
END LOOP;
RETURN;
END;
/
BEGIN
sendmail.send(
p_sender => 'alert@yourcompany.com',
p_recipient => 'dba1@yourcompany.com,dba2@yourcompany.com',
p_subject => '【告警】生產庫表空間剩餘 < 10%',
p_body => '表空間告警
' ||
'表空間 USERS 剩餘空間僅 8.2%,請立即擴容。
' ||
''
);
END;
/
在 common 後續寫入 MIME-Version: 1.0、Content-Type: multipart/mixed,再分 Boundary 寫入 HTML 部分與 Base64 編碼的附件部分。核心邏輯:
UTL_SMTP.WRITE_DATA(c, 'Content-Type: multipart/mixed; boundary="====BOUNDARY===="' || UTL_TCP.CRLF);
-- HTML 部分
UTL_SMTP.WRITE_DATA(c, '--====BOUNDARY====' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'Content-Type: text/html; charset=utf-8' || UTL_TCP.CRLF || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, p_html_body || UTL_TCP.CRLF);
-- 附件部分
UTL_SMTP.WRITE_DATA(c, '--====BOUNDARY====' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'Content-Type: application/pdf; name="report.pdf"' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'Content-Transfer-Encoding: base64' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'Content-Disposition: attachment; filename="report.pdf"' || UTL_TCP.CRLF || UTL_TCP.CRLF);
UTL_SMTP.WRITE_RAW_DATA(c, UTL_ENCODE.BASE64_ENCODE(p_blob)); -- 需分塊寫入
UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF || '--====BOUNDARY====--' || UTL_TCP.CRLF);
| 錯誤 | 原因 | 解決 |
|---|---|---|
| ORA-29278: SMTP transient error | SMTP 伺服器拒絕/超時/認證失敗 | 檢查防火牆、SMTP 白名單、是否需認證 (UTL_SMTP 不支援 AUTH,需繞過或用 Java Stored Procedure) |
| ORA-29540: class not found | UTL_TCP/UTL_SMTP 未安裝 | 執行 utltcp.sql、utlsmtp.sql |
| 中文亂碼 | 缺 charset=utf-8 或編碼不一致 | Header 加 charset=utf-8,DB 字符集為 AL32UTF8 |
| 大附件發送失敗 | SMTP 限制單郵大小 / UTL_SMTP 緩衝區限制 | 分卷壓縮、或改用外部郵件網關 API |
UTL_SMTP 雖屬低階 API,但透過模組化封裝 (Common + Send) 可構建穩健、可擴展的郵件發送服務。**生產環境建議**: