PL/SQLからメールを送信できるようにしておくと、夜間バッチの完了通知、エラー検知、監査レポート、問い合わせ受付の自動応答などをデータベース側で完結できます。Oracleには、簡単な通知に向く UTL_MAIL と、SMTP会話を細かく制御できる UTL_SMTP があります。
ただし、メール送信は「コードを書けば動く」だけの機能ではありません。ネットワークACL、SMTPサーバ、認証、STARTTLS、Wallet、文字コード、添付ファイルサイズ、誤送信防止、失敗時のリトライ設計まで含めて確認する必要があります。ジョブ通知として使う場合は DBMS_SCHEDULER完全ガイド、失敗履歴を残す設計は ロギングとトレーサビリティを両立する監査設計 もあわせて確認してください。
UTL_MAILとUTL_SMTPの使い分け- ネットワークACLと
SMTP_OUT_SERVERの準備 - テキストメール、HTMLメール、添付メールの基本形
- STARTTLS、AUTH、Walletを使う場合の注意
ORA-24247、ORA-29278、ORA-29279の切り分け- 本番運用での誤送信防止・再送・ログ設計
UTL_MAILとUTL_SMTPの使い分け
まず、どちらを使うかを決めます。UTL_MAIL は高レベルAPIで、単純なメール送信を短いコードで書けます。一方、UTL_SMTP はSMTPコマンドを扱う低レベルAPIで、認証、STARTTLS、マルチパート、細かなヘッダー制御が必要な場合に向きます。
前提準備:ネットワークACLを設定する
Oracle DatabaseからSMTPサーバへ接続するには、データベースユーザーに外部ネットワーク接続権限が必要です。11g以降では DBMS_NETWORK_ACL_ADMIN を使い、接続先ホストとポートに対して connect 権限を付与します。
注意点として、resolve 権限はポート範囲付きのACEに混ぜない方が安全です。DNS名前解決の権限はホスト単位で付与し、SMTP接続の connect はポート範囲付きで付与する、と分けて書くと公式仕様に沿った形になります。
-- DBA権限で実行する例
BEGIN
-- SMTP接続権限。587番ポートだけ許可する
DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
host => 'smtp.example.com',
lower_port => 587,
upper_port => 587,
ace => xs$ace_type(
privilege_list => xs$name_list('connect'),
principal_name => 'APP_USER',
principal_type => xs_acl.ptype_db
)
);
-- 名前解決権限。resolveはポート範囲を指定しない
DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
host => 'smtp.example.com',
ace => xs$ace_type(
privilege_list => xs$name_list('resolve'),
principal_name => 'APP_USER',
principal_type => xs_acl.ptype_db
)
);
END;
/
ORA-24247: network access denied by access control list が出る場合は、まずACLを疑います。ユーザー名、ホスト名、ポート番号、PDB/CDBのどこで実行しているか、DBA権限で設定したかを確認してください。
UTL_MAILを使う準備
UTL_MAIL は便利ですが、環境によってはデフォルトでインストールされていません。DBAが utlmail.sql と prvtmail.plb を実行し、初期化パラメータ SMTP_OUT_SERVER を設定し、利用スキーマへ EXECUTE 権限を付与します。
-- SYSまたはDBA作業の例。実行場所は環境に合わせて確認する @$ORACLE_HOME/rdbms/admin/utlmail.sql @$ORACLE_HOME/rdbms/admin/prvtmail.plb ALTER SYSTEM SET smtp_out_server = 'smtp.example.com:25' SCOPE=BOTH; GRANT EXECUTE ON UTL_MAIL TO app_user;
SMTP_OUT_SERVER は UTL_MAIL が使うSMTPサーバです。認証やSTARTTLSが必須のSMTPサーバでは UTL_MAIL だけで完結しにくいため、UTL_SMTP を検討します。
UTL_MAILでテキストメールを送る
単純な社内通知なら、UTL_MAIL.SEND が最も短く書けます。差出人、宛先、件名、本文、MIMEタイプを指定して送信します。
BEGIN
UTL_MAIL.SEND(
sender => 'batch@example.com',
recipients => 'ops@example.com',
subject => '日次バッチ完了',
message => '日次バッチが正常終了しました。',
mime_type => 'text/plain; charset=UTF-8'
);
END;
/
日本語メールでは、SMTPサーバやメールクライアント側の扱いも含めて文字化け確認が必要です。件名や本文のエンコード要件が厳しい場合、UTL_SMTP でヘッダーを明示的に組み立てた方が制御しやすくなります。
UTL_MAILでHTMLメールを送る
HTMLメールを送る場合は、mime_type に text/html を指定します。ただし、業務通知では見た目よりも到達性と読めることが大事です。HTMLだけでなく、必要ならテキスト版も用意します。
BEGIN
UTL_MAIL.SEND(
sender => 'batch@example.com',
recipients => 'ops@example.com',
subject => 'エラー検知',
message => '<html><body><h1>エラー検知</h1><p>詳細はログを確認してください。</p></body></html>',
mime_type => 'text/html; charset=UTF-8'
);
END;
/
UTL_SMTPでSTARTTLSとAUTHを扱う
認証必須のSMTPサーバやSTARTTLSが必要な環境では、UTL_SMTP を使います。重要なのは、STARTTLSを実行したあとに再度 EHLO を送ることです。TLS開始後にSMTPサーバが提示する拡張機能が変わるため、認証前に再確認します。
DECLARE
c UTL_SMTP.CONNECTION;
BEGIN
c := UTL_SMTP.OPEN_CONNECTION(
host => 'smtp.example.com',
port => 587,
wallet_path => 'file:/u01/app/oracle/wallets/smtp',
wallet_password => 'wallet_password'
);
UTL_SMTP.EHLO(c, 'db.example.com');
UTL_SMTP.STARTTLS(c);
-- STARTTLS後は再度EHLOする
UTL_SMTP.EHLO(c, 'db.example.com');
UTL_SMTP.AUTH(
c => c,
username => 'smtp_user',
password => 'smtp_password',
schemes => UTL_SMTP.ALL_SCHEMES
);
UTL_SMTP.MAIL(c, 'batch@example.com');
UTL_SMTP.RCPT(c, 'ops@example.com');
UTL_SMTP.OPEN_DATA(c);
UTL_SMTP.WRITE_DATA(c, 'From: batch@example.com' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'To: ops@example.com' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'Subject: STARTTLS test' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'Content-Type: text/plain; charset=UTF-8' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(c, 'メール送信テストです。' || UTL_TCP.CRLF);
UTL_SMTP.CLOSE_DATA(c);
UTL_SMTP.QUIT(c);
EXCEPTION
WHEN OTHERS THEN
BEGIN
UTL_SMTP.QUIT(c);
EXCEPTION
WHEN OTHERS THEN NULL;
END;
RAISE;
END;
/
Walletのパス、証明書、TLSバージョン、SMTPサーバの認証方式は環境差が大きい部分です。クラウドSMTPではアプリパスワード、IP制限、送信元ドメイン認証、送信レート制限も確認します。
添付ファイルを送るときの考え方
添付ファイルは、サイズ、文字コード、MIME境界、Base64エンコード、メールサーバ側の上限を考慮します。小さなテキストやCSVなら UTL_MAIL で足りる場合がありますが、大きな添付や複数パートを正確に制御したい場合は UTL_SMTP で組み立てます。
BEGIN
UTL_MAIL.SEND_ATTACH_VARCHAR2(
sender => 'batch@example.com',
recipients => 'ops@example.com',
subject => '集計CSV',
message => '集計結果を添付します。',
mime_type => 'text/plain; charset=UTF-8',
attachment => 'id,name,amount' || CHR(10) ||
'1,Sample,1000',
att_inline => FALSE,
att_mime_type => 'text/csv; charset=UTF-8',
att_filename => 'summary.csv'
);
END;
/
CLOB/BLOBを扱う添付処理では、VARCHAR2の上限や変換バッファ不足に注意します。LOBの読み書きは DBMS_LOB完全ガイド、CLOB/BLOB変換時の ORA-22835 は ORA-22835の原因と解決方法 が参考になります。
ジョブ失敗通知として使う
メール送信は、単体で使うよりジョブ管理・ログ管理と組み合わせると実務価値が出ます。ジョブが失敗したら即メール、成功時は日次サマリだけ送る、一定回数失敗したら通知を抑制する、といった運用ルールを決めます。
CREATE OR REPLACE PROCEDURE notify_job_error(
p_job_name IN VARCHAR2,
p_message IN VARCHAR2
) AS
BEGIN
INSERT INTO job_notice_log(job_name, message, created_at)
VALUES (p_job_name, p_message, SYSDATE);
UTL_MAIL.SEND(
sender => 'batch@example.com',
recipients => 'ops@example.com',
subject => '[ERROR] ' || p_job_name,
message => p_message,
mime_type => 'text/plain; charset=UTF-8'
);
EXCEPTION
WHEN OTHERS THEN
-- メール送信失敗で本処理の原因を消さないよう、別ログに残す
INSERT INTO mail_error_log(error_message, created_at)
VALUES (SQLERRM, SYSDATE);
END;
/
メール送信そのものが失敗することもあります。エラー通知のためのメール送信が失敗したときに本来の障害情報を失わないよう、ログテーブルへ必ず残します。実行履歴の集中管理は ジョブ実行履歴の集中管理と異常検知、デバッグ出力の基本は DBMS_OUTPUTの使い方 も役立ちます。
よくあるエラーと切り分け
-- まずDB内の設定と権限を確認する SHOW PARAMETER smtp_out_server SELECT host, lower_port, upper_port, ace_order FROM dba_host_aces WHERE host = 'smtp.example.com'; -- 送信処理の例外はSQLCODE/SQLERRMだけでなく、SMTP応答もログ化する CREATE TABLE mail_send_log ( log_id NUMBER GENERATED ALWAYS AS IDENTITY, send_to VARCHAR2(4000), subject VARCHAR2(500), status VARCHAR2(30), error_code NUMBER, error_message VARCHAR2(4000), created_at DATE DEFAULT SYSDATE );
誤送信防止の設計
本番メール送信で最も怖いのは、技術的なエラーより誤送信です。検証環境から本番宛に送る、個人情報を添付する、宛先リストを間違える、障害時に同じ通知を大量送信する、といった事故を防ぐ仕組みを入れます。
[DEV]、[STG]、[PROD] を付けます。CREATE OR REPLACE FUNCTION safe_recipient(
p_recipient IN VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
IF SYS_CONTEXT('USERENV', 'DB_NAME') <> 'PRODDB' THEN
RETURN 'dev-notice@example.com';
END IF;
IF NOT REGEXP_LIKE(p_recipient, '^[A-Za-z0-9._%+-]+@example\.com$') THEN
RAISE_APPLICATION_ERROR(-20030, 'recipient is not allowed');
END IF;
RETURN p_recipient;
END;
/
本番導入前チェックリスト
connect と resolve を正しく付与しているか。まとめ
PL/SQLでメール送信を自動化するなら、単純な社内通知は UTL_MAIL、認証・STARTTLS・添付・細かなヘッダー制御が必要な場合は UTL_SMTP を選ぶのが基本です。どちらを使う場合も、ネットワークACL、SMTPサーバ設定、文字コード、ログ、誤送信防止をセットで設計します。
特に本番では、ORA-24247 のACL不足、ORA-29278 / ORA-29279 のSMTP応答、STARTTLS後のEHLO再実行、Wallet証明書、宛先制御が重要です。メールは障害を知らせる最後の通路になりやすいので、送信できない場合のログと代替確認手段まで用意しておきましょう。

