2020-07-22

PL/SQLから日本語(JIS)でメール送信する

ここでは、

にあるサンプルコード
DECLARE
  c UTL_SMTP.CONNECTION;
 
  PROCEDURE send_header(name IN VARCHAR2, header IN VARCHAR2) AS
  BEGIN
    UTL_SMTP.WRITE_DATA(c, name || ': ' || header || UTL_TCP.CRLF);
  END;
 
BEGIN
  c := UTL_SMTP.OPEN_CONNECTION('smtp-server.acme.com');
  UTL_SMTP.HELO(c, 'foo.com');
  UTL_SMTP.MAIL(c, 'sender@foo.com');
  UTL_SMTP.RCPT(c, 'recipient@foo.com');
  UTL_SMTP.OPEN_DATA(c);
  send_header('From',    '"Sender" <sender@foo.com>');
  send_header('To',      '"Recipient" <recipient@foo.com>');
  send_header('Subject', 'Hello');
  UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF || 'Hello, world!');
  UTL_SMTP.CLOSE_DATA(c);
  UTL_SMTP.QUIT(c);
EXCEPTION
  WHEN utl_smtp.transient_error OR utl_smtp.permanent_error THEN
    BEGIN
      UTL_SMTP.QUIT(c);
    EXCEPTION
      WHEN UTL_SMTP.TRANSIENT_ERROR OR UTL_SMTP.PERMANENT_ERROR THEN
        NULL; -- When the SMTP server is down or unavailable, we don't have
              -- a connection to the server. The QUIT call raises an
              -- exception that we can ignore.
    END;
    raise_application_error(-20000,
      'Failed to send mail due to the following error: ' || sqlerrm);
END;

を、日本語JISコード(ISO-2022-JP)で送れるように書き換えてみます。
ついでに、SMTP認証あり、ポート番号を587に変更し、プロシージャ化しています。

CREATE OR REPLACE PROCEDURE SEND_MAIL(
    IN_FROM_NAME        VARCHAR2,
    IN_FROM_ADDRESS     VARCHAR2,
    IN_TO_ADDRESS       VARCHAR2,
    IN_SUBJECT          VARCHAR2,
    IN_MESSAGE          VARCHAR2,
    IN_AUTH_USER        VARCHAR2,
    IN_AUTH_PASSWORD    VARCHAR2
) AS
    c   UTL_SMTP.CONNECTION;
    host    CONSTANT VARCHAR2(64) := 'foo.com';
    port    CONSTANT NUMBER := 587;
    -- ヘッダー部送信
    -- UTL_ENCODE.MIMEHEADER_ENCODE(buf, 'ISO2022-JP')を使うと=?iso-2022-jp?B?が=?ISO2022-JP?B?になるため環境により文字化けする。
    -- Each line of characters MUST be no more than 998 characters, and SHOULD be no more than 78 characters, excluding the CRLF.(RFC 2822 2.1.1)
    PROCEDURE SEND_HEADER (
         IN_NAME IN VARCHAR2
        ,IN_HEADER IN VARCHAR2
        ,IN_ADDRESS IN VARCHAR2 := NULL
    ) AS
    BEGIN
        UTL_SMTP.WRITE_DATA(c, IN_NAME || ': ');
        UTL_SMTP.WRITE_DATA(c, '=?iso-2022-jp?B?');
        UTL_SMTP.WRITE_RAW_DATA(c, UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(CONVERT(IN_HEADER, 'ISO2022-JP'))));
        UTL_SMTP.WRITE_DATA(c, '?=');
        IF IN_ADDRESS IS NOT NULL THEN
            UTL_SMTP.WRITE_DATA(c, '<' || IN_ADDRESS || '>');
        END IF;
        UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF);
    END;

BEGIN
    c := UTL_SMTP.OPEN_CONNECTION(host, port);
    UTL_SMTP.EHLO(c, host);

    -- SMTP認証(PLAIN)
    -- 11.2.0.1以前
    --UTL_SMTP.COMMAND(c, 'AUTH', 'PLAIN ' || UTL_ENCODE.TEXT_ENCODE(CHR(0)|| IN_AUTH_USER || CHR(0)|| IN_AUTH_PASSWORD, NULL, UTL_ENCODE.BASE64));
    -- 11.2.0.2以上
    UTL_SMTP.AUTH(c => c, username => IN_AUTH_USER, password => IN_AUTH_PASSWORD, schemes => UTL_SMTP.all_schemes);

    UTL_SMTP.MAIL(c, IN_FROM_ADDRESS);
    UTL_SMTP.RCPT(c, IN_TO_ADDRESS);

    UTL_SMTP.OPEN_DATA(c);

    --ヘッダ
    SEND_HEADER('From', IN_FROM_NAME, IN_FROM_ADDRESS);
    UTL_SMTP.WRITE_DATA(c, 'To: ' || IN_TO_ADDRESS || UTL_TCP.CRLF);
    SEND_HEADER('Subject', IN_SUBJECT);

    UTL_SMTP.WRITE_DATA(c, 'MIME-Version: 1.0' || UTL_TCP.CRLF);
    UTL_SMTP.WRITE_DATA(c, 'Content-Type: text/plain; charset=iso-2022-jp' || UTL_TCP.CRLF);
    UTL_SMTP.WRITE_DATA(c, 'Content-Transfer-Encoding: 7bit' || UTL_TCP.CRLF);

    UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF);
    -- 本文
    UTL_SMTP.WRITE_RAW_DATA(c, UTL_RAW.CAST_TO_RAW(CONVERT(IN_MESSAGE, 'ISO2022-JP')));
    UTL_SMTP.CLOSE_DATA(c);
    UTL_SMTP.QUIT(c);

EXCEPTION
    WHEN UTL_SMTP.transient_error OR UTL_SMTP.permanent_error THEN
        BEGIN
            UTL_SMTP.QUIT(c);
        EXCEPTION
            WHEN UTL_SMTP.TRANSIENT_ERROR OR UTL_SMTP.PERMANENT_ERROR THEN
                NULL; -- When the SMTP server is down or unavailable, we don't have
                            -- a connection to the server. The QUIT call will raise an
                            -- exception that we can ignore.
        END;
        raise_application_error(-20000,
            'Failed to send mail due to the following error: ' || sqlerrm);
END;
/


  • ヘッダのエンコードにはUTL_ENCODE.MIMEHEADER_ENCODEを使いたいところですが、キャラクタ・セットが iso-2022-jp ではなく ISO2022-JP と出力されてしまい、環境によって文字化けするため、使わないようにしています。
  • RFC2822では1行の文字数について、CRLFを除く998未満(MUST)、78文字未満(SHOULD)と規定されており、UTL_ENCODE.MIMEHEADER_ENCODEを使えば1行当たり78文字未満に分割(folding)してくれるのですが、上記のコードでは考慮していませんのでご注意ください。