24 April 2009

(PHP) Net_SMTPおよびNet_Mailでのメール送信

PHPでの日本語メールの送信方法メモ。

RFC2822 (Internet Message Format)で、行の長さの制限 この仕様が 1 行の文字数に課す制限は二つある。CRLF を除いて、各行は 998 文字を超えてはならならず(MUST)、78 文字を超えるべきではない(SHOULD)。 となっているため、長い文章の自動改行処理を行わないといけない。

また、日本語は全てJIS('ISO-2022-JP)に変換するとともに、題名(Subject)はMIMEエンコードしなければならない。
さらに、2バイト文字コードのうち、2バイト目が”5C”の場合に、自動改行処理等で用いている mb_XXXX系のPHP関数がエラーを起こすので、スクリプトはUTF-8で保存する。

※ Net_SMTP版とNet_mime版の差異は、ヘッダ部分の『Content-Type』属性が、Net_mime版ではクオーテーションマークで属性が囲まれる。たとえば、『charset="ISO-2022-JP"』のようになる。この表記で読み取り川でエラーが発生する可能性もある。(例:PearのNet_IMAPではエラーになる)

PearのNet_SMTPパッケージのみを利用する場合


<?php

// PEARの Net_SMTPコンポーネントを用いる
require_once("Net/SMTP.php");

// 言語と文字コードの設定
mb_language('Japanese');
mb_internal_encoding('ISO-2022-JP');
mb_http_output('UTF-8');

/* SMTPサーバ名、送受信者アドレスの指定 */
$host = 'mail.example.com';
$from = 'sample@example.com';
$rcpt = 'user@gmail.com';
$smtp_user = 'user';
$smtp_password = 'password';

/* 新しい Net_SMTP オブジェクトを作成します */
if (! ($smtp = new Net_SMTP($host))) {
die("ネットワーク異常(SMTPオブジェクト作成不能)");
}

/* デバッグ出力を、stdoutに送る */
// $smtp->setDebug(false);

/* SMTP サーバに接続します */
if (PEAR::isError($e = $smtp->connect())) {
die("ネットワーク異常(SMTPでコネクション不能)");
}

/* ユーザ名、パスワードを用いてSMTPログオンします */
if (PEAR::isError($e = $smtp->auth($smtp_user, $smtp_password, 'LOGIN'))) {
$smtp->disconnect();
die("SMTPサーバの認証に失敗");
}

/* SMTP コマンド 'MAIL FROM:' を送信します */
if (PEAR::isError($smtp->mailFrom($from))) {
$smtp->disconnect();
die("送信者アドレスがSMTPサーバで許可されませんでした");
}

/* 受信者のアドレスを指定します */
if (PEAR::isError($res = $smtp->rcptTo($rcpt))) {
$smtp->disconnect();
die("受信者アドレスがSMTPサーバで許可されませんでした");
}

/* 本文テキスト部分を 75 桁で折り返し処理 */
// 文字列を一旦JISコードに変換する(全角=2バイト、半角=1バイトで扱うため)
$strMessage = mb_convert_encoding($_POST['message'], 'ISO-2022-JP', 'UTF-8');
$strMessageWrap = "";
$nCountCol = 0;
for($i=0; $i<mb_strlen($strMessage); $i++)
{
$substr= mb_substr($strMessage, $i, 1, 'ISO-2022-JP');
$strMessageWrap .= $substr;
if($substr == "\n") $nCountCol = 0; // 改行コードを検知した場合は、桁数をリセット
$nCountCol += strlen($substr)==1?1:2; // 切り出された文字のバイト数
if($nCountCol >= 76)
{ // 76桁目で改行コードを挿入
$nCountCol=0;
$strMessageWrap .= "\n";
}
}

/* メールの本文を設定します */
$strBody = "User-Agent: PHP Pear Net_SMTP\n";
$strBody .= "To: user <user@gmail.com>\n";
$strBody .= "From: sample <sample@example.com>\n";

// 題名はMIMEエンコードする
$strBody .= 'Subject: ' . mb_encode_mimeheader(mb_convert_encoding($_POST['subject'], 'ISO-2022-JP', 'UTF-8'), 'ISO-2022-JP', 'Q') . "\r\n";

$strBody .= "Content-Type: text/plain; charset=ISO-2022-JP\n";
$strBody .= "Content-Transfer-Encoding: 7bit\n";
$strBody .= "\n\n"; // 本文の前に改行コードを送る

// 本文はJISエンコード(桁数揃えのところで既に変換済みのため、念のため)
$strBody .= mb_convert_encoding($strMessageWrap, 'ISO-2022-JP', 'auto');

if (PEAR::isError($smtp->data($strBody))) {
$smtp->disconnect();
die("本文の送信に失敗");
}

/* SMTP サーバとの接続を切断します */
$smtp->disconnect();

次に、Net_Mail と Net_mime コンポーネントを用いる場合


<?php

// PEARの Net_SMTPコンポーネントを用いる
require_once("Net/SMTP.php");

// 言語と文字コードの設定
mb_language('Japanese');
mb_internal_encoding('ISO-2022-JP');
mb_http_output('UTF-8');

/* SMTPサーバ名、送受信者アドレスの指定 */
$host = 'mail.example.com';
$from = 'sample@example.com';
$rcpt = 'user@gmail.com';
$smtp_user = 'user';
$smtp_password = 'password';

/* 新しい Mail_Mime オブジェクトを作成します */
$mime = new Mail_mime("\n");

/* 本文テキスト部分を 75 桁で折り返し処理 */
// 文字列をJISコードに変換する(全角=2バイト、半角=1バイトで扱うため)
$strMessage = mb_convert_encoding($_POST['message'], 'ISO-2022-JP', 'UTF-8');
$strMessageWrap = "";
$nCountCol = 0;
for($i=0; $i<mb_strlen($strMessage); $i++)
{
$substr= mb_substr($strMessage, $i, 1, 'ISO-2022-JP');
$strMessageWrap .= $substr;
if($substr == "\n") $nCountCol = 0; // 改行コードを検知した場合は、桁数をリセット
$nCountCol += strlen($substr)==1?1:2; // 切り出された文字のバイト数
if($nCountCol >= 76)
{ // 76桁目で改行コードを挿入
$nCountCol=0;
$strMessageWrap .= "\n";
}
}

/* 本文を$mimeにセット */
$strMessageWrap = "\n\n" . $strMessageWrap; // データ開始前に改行
$mime->setTxtBody(mb_convert_encoding($strMessageWrap, 'ISO-2022-JP', 'auto'));

/* ヘッダ情報を$mimeにセット*/
$headers = array(
"User-Agent" => 'PHP Pear Net_mime',
"To" => mb_encode_mimeheader(mb_convert_encoding($rcptname, 'ISO-2022-JP', 'UTF-8'))."<".$rcpt.">",
"From" => mb_encode_mimeheader(mb_convert_encoding($fromname, 'ISO-2022-JP', 'UTF-8'))."<".$from.">",
"Subject" => mb_encode_mimeheader(mb_convert_encoding($_POST['subject'], 'ISO-2022-JP', 'UTF-8'), 'ISO-2022-JP', 'Q')
);

/* 文字エンコード */
$param = array(
'head_charset' => 'ISO-2022-JP'
'text_charset' => 'ISO-2022-JP',
'text_encoding' => '7bit',
);

/* Mail_Mime オブジェクトを構築 */
$body = $mime->get($param);
$headers = $mime->headers($headers);

$smtp =
array(
'host' => $host,
'port' => 25,
'auth' => TRUE,
'username' => $smtp_user,
'password' => $smtp_password,
'debug' => FALSE,
);

/* 新しいMailオブジェクトを作成 */
$mail =& Mail::factory('smtp', $smtp);

/* メールの送信 */
if (PEAR::isError($mail->send($rcpt, $headers, $body)))
{
die("送信エラー発生\n");
}