弊社が提供している同報メールやステップメールの配信はどのドメインの専用サーバーまたはVPSで運用されています。SMTPサーバーからメールが発信され、不達の場合は送付先サーバーからエラーが戻ってきます。
エラーにはそもそもドメインが違っていたり、アカウントが違っていたり、すでにそのドメインのサーバーがなくなっているというケースもあります。
このデータを定期的にシステムが監視して持っているリストを自動クリーニングする仕組みをもっていました。
ただ、時々Googleなどがブロックしてすべてのgmailアドレスに不達になるケースがあり、この仕組みをうまく運用できず結局リストから手動で不達リストを抽出してリストから除外する処理を行っていたりもします。
この不達には一時的なものと恒久的なものがあるので、一時的なものは不達リストから除外する必要があります。
除外のケースは2つあり、ひとつはメールボックスがいっぱいで受信できなかったケース、もう一つは一時的な接続エラーです。
接続エラーにも削除対象とすべきエラーと一時的なものとして除外するものがあります。
メールボックスがいっぱいの際のメッセージ
接続先のメールサーバーが出すメッセージなので、接続先のメールサーバーの種類によってメッセージが異なります。
Postfix
451 4.3.0 <recipient@example.com>: Recipient address rejected: mailbox full
Sendmail
550 5.2.2 <recipient@example.com>… Mailbox full
Exim
550 5.2.2 Mailbox is full / Blocks limit exceeded / Inode limit exceeded
Microsoft Exchange Serve
452 4.2.2 Mailbox full
Courier Mail
Mailbox quota exceeded
Qmail
552 sorry, that message size exceeds my databytes limit (#5.3.4)
その他にもフリーメールサーバーは別途個別メッセージを戻してきます。
Gmail
52-5.2.2 The email account that you tried to reach is over quota. Please direct
552-5.2.2 the recipient to
552 5.2.2 https://support.google.com/mail/?p=OverQuotaPerm v23si4197268pfi.149 – gsmtp
Yahoo Mail
554 5.2.2 Mailbox full
Outlook.com
550 5.2.2 <recipient@outlook.com> mailbox full
ネットワークの接続エラーメッセージ
DNS解決失敗
status=deferred (delivery temporarily suspended: Host or domain name not found. Name service error for name=example.com type=A: Host not found, try again)
タイムアウト
status=deferred (connect to mail.example.com[192.0.2.1]:25: Connection timed out)
接続拒否
status=deferred (connect to mail.example.com[192.0.2.1]:25: Connection refused)
ネットワーク障害
status=deferred (connect to mail.example.com[192.0.2.1]:25: No route to host)
TLSハンドシェイク失敗
status=deferred (Server certificate not trusted)
PHPで不達リストを生成する
メールログから status=(bounced|deferred) を含む行を取り出し、その中からメールボックスがいっぱいのものは除外し、メールアドレス一覧として出力するPHPです。そもそもログがroot権限でないと閲覧できないので、コピーなりしてPHPプログラムから読み込めるようにする必要があります。
メールボックスフルのパターンは quota|full|space|size exceeds|Server certificate not trusted でよさそうです。
ネットワークの一時的な接続切れパターンは timed out|No route to host|
<?php
// ログファイルのパスを設定
$logFile = '/var/log/maillog';
// ログファイルが存在するか確認
if (!file_exists($logFile)) {
die("Log file does not exist.");
}
// ログファイルを読み取り専用で開く
$handle = fopen($logFile, 'r');
if (!$handle) {
die("Unable to open log file.");
}
// 不達メールのパターン
$pattern = '/^(\w{3} \d{1,2} \d{2}:\d{2}:\d{2}) .*?postfix\/smtp\[.*?\]: .*? to=<([^>]+)>, .*? status=(bounced|deferred) \((.*)\)/';
// 除外不達メールのパターン
$pass_pattern = '/quota|full|space|size exceeds|quota|full|space|size exceeds|Server certificate not trusted/';
// 抽出したデータを格納する配列
$undeliveredEmails = [];
// ログファイルを行ごとに読み込む
while (($line = fgets($handle)) !== false) {
// パターンにマッチする行を検索
if (preg_match($pattern, $line, $matches)) {
if(preg_match($pass_pattern, $line, $matches2)) {
continue;
}
// 日付、メールアドレス、エラー理由を抽出
$date = $matches[1];
$email = $matches[2];
$status = $matches[3];
$reason = $matches[4];
// データを配列に追加
$undeliveredEmails[] = ['date' => $date, 'email' => $email, 'status' => $status, 'reason' => $reason];
$uniqu_email[$email]++;
}
}
// ファイルを閉じる
fclose($handle);
// 結果を表示
foreach ($undeliveredEmails as $entry) {
// echo "Date: " . $entry['date'] . " - Email: " . $entry['email'] . " - Reason: " . $entry['reason'] . "\n";
}
foreach($uniqu_email as $key => $value){
echo "$key\n";
}
?>
最初はコメントアウトされている
// echo “Date: ” . $entry[‘date’] . ” – Email: ” . $entry[‘email’] . ” – Reason: ” . $entry[‘reason’] . “\n”;
を有効にしてメッセージを確認した方が無難です。
リストみて問題なさそうなら
echo “$key\n”; の方でメールアドレスだけを出力するようにします。
出来上がったリストを無効化処理して完了です。