あなたの作ったメール配信システムはエラーメール処理をしていますか?

今回はメルマガ等やメーリングリストのように大量のメールを配信するためのメール配信システムを自前で開発している方向けの情報(備忘録?)です。

大量のメールを配信する場合、配信できなかったエラーメールを適切に処理することが重要です。たとえば、

「unknown user というエラーが帰ってくるのであれば、そのユーザは存在しないので今後メールを配信しないようにする」

なんて感じです。適切なエラーメール処理なくば、知らず知らずのうちに SPAMer と同じようなメール配信をしていることになってしまうのです。かく言う僕の作ったメール配信システムも、それほどエラーメール処理を厳密に行っているわけではなく、何とかしないとなぁ〜と思っている今日この頃で、ちまちま資料を集め始めて仕様検討している次第です。

- スポンサーリンク -

メール送信における socket データに関しての技術メモ

通常、メールを送信するには最低限下記の順序でコマンドを発行している。

コマンド 意味
HELO ドメイン名 SMTPサーバが接続してきたクライアントを認識する。
MAIL FROM: 送信元メールアドレス 送信元のメールアドレスを指定する(Envelope From)。 ※しばしばメールヘッダの「Return-Path:」として記録される。
RCPT TO: 送信先メールアドレス 送信先のメールアドレスを指定する(Envelope To)。 宛先がTO、CC、BCC に関係なく、複数の送信先がある場合はこのコマンドを繰り返す。 ※このコマンドで指定したアドレスにメールが送信される。 ※しばしばメールヘッダの「Received:」として記録される。
DATA サーバに本文の送信開始を宣言する。 ※DATA部のTO、CC、BCC は見た目だけ。RCPT TO: と混同しないように。
QUIT セッション終了を宣言する。

メールを送信に直接関係しないコマンドとしては下記のものがある。

コマンド 意味
ETRN ホスト名・ドメイン名・キュー名 指定されたホスト名・ドメイン名・キュー名について、メールキューを作成する。
RSET MAIL、SEND、SOML、SAMLコマンドにより開始された処理を中止し、サーバ内部の状態をリセットする。
NOOP なにもしない。サーバの動作を確認するためのコマンド。

メールサーバによっては実装されていない可能性のあるコマンドとしては下記のものがある。

コマンド 意味
EHLO ドメイン名 HELOのESMTP版コマンド。ESMTPの機能を利用する場合はこのコマンドを利用する。
VRFY ユーザー名・メールアドレス 受信側SMTPサーバにユーザ名が存在するか確認を行う。
EXPN メールアドレス ユーザのaliasを展開し、表示する。
VERB 応答を詳細モードにする。
TURN クライアントとサーバの役割の交換を要求する。
HELP [コマンド名] 受信側SMTPサーバがサポートしているコマンドの一覧と説明を表示する。

メールヘッダーの内容に関しての技術メモ

Header 意味
From: 送信元のメールアドレス。
To: 送信先のメールアドレス。複数のメールアドレスが指定された場合、「,(カンマ)」で区切る。
Cc: 「Carbon Copy」の略で、「To:」で指定した宛先に送信されるメールと同じ内容のものを、「Cc:」で指定した宛先にも送信することが出来る。
Date: メールが送信された時刻を GMTを基準にして表す。 時差は「+」「-」で表す。
Subject: メールの件名(題名)を表す。
Return-Path: メール配信エラー時に送信されるエラーメールの送信先アドレスを表す。
Received: メールが配送されたルートを表す。配送経路は下から上の順で見ていく。
Message-Id: メール1通ごとにふられたユニークな番号。
Reply-To: 受信したメールに対して返信をする際に「From: 」と異なるメールアドレスに返信させたい場合に宛先を指定する。
In-Reply-To: 送信元のメッセージID。
Errors-To: 何らかのエラーが発生した場合に、指定したメールアドレスにエラーメッセージを送信する。
MIME-Version: MIMEのバージョンを表します。現在はVersion1.0のみ。
Content-Length: メッセージ本文のバイト数を10進数で指定する。
Content-Type: メッセージ本文の種類(Text、Image、Audio、Video、Application、Multipart、Message)を指定する。
Content-Transfer-Encoding: エンコードの種類を指定する。
Organization: 送信元が何の組織に属しているかを指定する。
X-UIDL: POPサーバーが届いたメールを区別するために、メールごとに自動でつける場合がある。
Encrypted: 暗号化情報を指定する。

メールの応答コードに関しての技術メモ

エラー種別 コード 意味
2xx : 正常系 200 RFC標準以外の成功応答
214 システム・ステータスまたはシステム・ヘルプ応答
214 ヘルプ・メッセージ、コマンドの使用方法
220 SMTPサービス準備完了
221 コネクションのクローズ(QUITコマンドに対する応答)
250 リクエストされたコマンドが正常終了
251 受信者がローカルユーザーでないが転送可能
252 VRFYコマンド利用不可
3xx : 正常系:コマンド受け入れ後にさらに入力が必要 354 メッセージを送信し"\n . \n"で終了して下さい
4xx : 一時的なエラー.再度実行すれば成功する可能性がある 421 指定ドメインのSMTPサービスが動作不能のためコネクションを切断
450 メールボックス利用不可のため失敗
451 ローカルサーバエラーのため、コマンドの実行に失敗
452 容量不足のため、リクエストされたメールの受信に失敗
5xx : 恒久的なエラー.問題を修正しなければ成功しない 500 コマンドの文法エラーか不明なコマンド
501 コマンド引数エラー
502 コマンドが未実装
503 コマンド順序が正しくない
504 コマンドパラメーターが未実装
550 メールボックスが存在しないため失敗
551 ユーザーが指定のドメインに存在しない
552 クライアント記憶域割り当て超過によるコマンド中止
553 メールボックス名が無効
554 メール転送処理に失敗
上記表の各コードに共通 x0x 文法エラー
x1x 付加メッセージ
x2x SMTP接続関連
x5x クライアント側の問題

メール送信エラーの原因と対処方法についての技術メモ

エラーメッセージ 対処方法
554 delivery error: dd This user doesn't have a yahoo.co.jpaccount.(受信者のメールアドレス) 指定した送信先が存在しないので、今後メールを送信してはならない。
554 delivery error: dd Sorry, your message to (受信者のメールアドレス) cannot be delivered. This account is over quota. 相手の方のメールボックスの保存容量が規定容量を超え、新しいメールを受け取れない状態。一時的にメールの送信を停止する必要がある。
(****@***.**.**): ***.***.***.*** does not like recipient. Remote host said: 550 Requested action not taken: mailbox unavailable Giving up on ***.***.***.***. 宛先のメールアドレスのメールボックスが何かの理由により使用不可となってい留状態。一時的にメールの送信を停止する必要がある。
***.***.***.*** does not like recipient. Remote host said: 550 ... User unknown Giving up on ***.***.***.***. 宛先のメールアドレスが間違っている、もしくは別のメールアドレスに変更されている可能性があるため、今後メールを送信してはならない。
(****@***.**.**): ***.***.***.*** does not like recipient. Remote host said: 554 (****@***.**.**): Recipient address rejected: Access denied Giving up on ***.***.***.***. 該当サーバーが宛先への中継が拒否されている状態。宛先のメールアドレス側で受信拒否設定を行なっている可能性があるため、、一時的に送信を停止する必要がある。
(****@***.**.**): ***.***.***.*** does not like recipient. Remote host said: 554 5.7.1 Relay denied Giving up on ***.***.***.***. 同上
(****@***.**.**): Connected to ***.***.***.*** but sender was rejected. Remote host said: 550 Access denied 送信者のアドレスが拒否されている状態。宛先のメールアドレス側で受信拒否設定を行なっている可能性があるため、一時的に送信を停止する必要がある。
(****@***.**.**): ***.***.***.*** does not like recipient. Remote host said: 550 Invalid recipient: (****@***.**.**): Giving up on ***.***.***.***. 宛先が間違っているため、無効なメールアドレスとして処理する。
(****@***.**.**): ***.***.***.*** does not like recipient. Remote host said: 553 (****@***.**.**)... No such user here Giving up on ***.***.***.***. 宛先のユーザーが送信先サーバーに存在しないため、無効なメールアドレスとして処理する。
(****@***.**.**): CNAME lookup failed temporarily. (#4.4.3) I'm not going to try again; this message has been in the queue too long. 宛先のドメインが正しくないため、しないため、無効なメールアドレスとして処理する。
(****@***.**.**): Sorry, I wasn't able to establish an SMTP connection. (#4.4.1) I'm not going to try again; this message has been in the queue too long. 宛先のサーバーは存在するが、メールの受け入れをしていない状態。一時的に送信を停止する必要がある。
(****@***.**.**): Sorry, I couldn't find any host named *****.**.** (#5.1.2) 宛先のドメインが正しくないため、しないため、無効なメールアドレスとして処理する。

エラーメールの送り先の落とし穴の技術メモ

Envelope-From と DATA部の From を混同しないよう注意。
いわゆるメーラーの From に表示されている値は、DATA部の From で単なる見た目です。何らかの理由で見た目の From を変更する場合でも、Envelope-From にはエラーメールを受け取りたいアドレスを指定する必要があります。間違えると、意図しないアドレスに大量のエラーメールが届く羽目になります。

エラーメールを自動処理する方法:qmail+Perl 編

僕が扱っているメールサーバに qmail なのですが、qmail は .qmailというファイルをホームディレクトリに用意することで、 Maildir にメールを保存する前にいろいろと処理をさせることができます。sendmail でいう .forward に相当するファイルになります。注意点は、パーミッションを 700 にする必要があるという点です。

メール転送の設定例 (エラーメールを他のアドレスに転送する)

echo '&err@drk7.jp'>~/.qmail

chmod 700 ~/.qmail

エラー自動処理の設定例 (エラーメールをパイプを使って perl-script で処理する)

echo '|/var/log/auto_errmail.pl >>/var/log/errmail.log'>~/.qmail

chmod 700 ~/.qmail

※ auto_errmail.pl ってのはエラーメールを適切に処理する perl-script を想定してまして、メーリングリストの fml8 っていうツールの Mail モジュールが流用できそうな感じです。

参考サイト一覧

- スポンサーリンク -