サーバーのメールログから不達リストを生成する

弊社が提供している同報メールやステップメールの配信はどのドメインの専用サーバーまたは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”; の方でメールアドレスだけを出力するようにします。

出来上がったリストを無効化処理して完了です。

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

株式会社ねこすけの代表をしています。
2005年に創業しWebマーケティングを実践するためのコンサルティング、サイト構築、サイト運用、システム開発を行っています。
会員・顧客属性を利用したコンテンツ管理を得意としており、協会サイト、多ブランドのECサイト、会員向けコンテンツサイトなどを構築運営しています。Webマーケティングを進めてるのにパートナーがほしいと感じている方、ご相談ください。
月に1度のミーティングから細かなサイト保守まで必要な部分での対応が可能です。 問い合わせ

目次