メールアドレスを王大人先生に確認してもらう方法(精度に若干の難あり?)

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
王大人死亡確認

この記事を三行にまとめると

送信直前までのプロセスを自作して確認する感じ
250か354が返ってくれば問題なくメールが送れるはず
このやり方はそこまで精度がよくありません
メールを送信する際、入力されたアドレスが間違っていたりいつの間にかアドレスが変更されていたりして送れないことがあると思います。そういう場合は送信エラーのメールが返ってきたりするので、それでダメだったかどうか確認すれば基本的には問題ないのですが、例えばメルマガを配信するようなシステムを運用している時にあまりにもエラーメールが多いと、メールサーバー自体の信用度が落ちて、場合によってはブラックリストに載ってしまうこともあるかもしれません。そうなると自分とこから配信するメールは、たとえ正しいメールアドレスに送信した場合であってもスパムかもしれないと思われて弾かれてしまうことがある。

そうならないようにはどうするのがベストかって言われると結構難しいところ……というか僕程度のレベルでは分からないってのが正直なところなのですが、一つの方法として、そのメールアドレスが実在するものかどうか送信前に確かめるという手があります。

簡単にいうと送信先のメールサーバーにソケットで接続して、メール送信直前までのプロセスを自作して確認する感じ。とりあえず実際にコードを書いてやってみましょう。



メールサーバーを確かめる

まずは送信相手のメールアドレスからメールサーバーの情報を取得します。

PHPには「getmxrr」という、DNSのMXレコードの情報を取れる大変便利な関数が存在するので、そいつを使ってメールサーバーのドメインを取得しましょう。

$to = 'akatsuki@norm-nois.com';
$domain = explode('@', $to);
$domain = end($domain);
getmxrr($domain, $mx);
print_r($mx);

//取得結果
Array
(
  [0] => mail.norm-nois.com
)

ここでは仮に送信先のメールアドレスを「akatsuki@norm-nois.com」としています。MXレコードの取得にはドメイン名(ホスト名)を入力する必要があるので、メールアドレスからドメイン部分(norm-nois.com)を抜き取ってgetmxrrで情報を取得しています。

メールサーバーはgetmxrrの第二引数(上記では$mx)に配列で入ってきます。一つしかない場合もあれば複数存在する場合もある。今回は「mail.norm-nois.com」という結果が返ってきたと仮定します。



ソケット通信

ほんじゃあさっきの結果を使ってソケット通信してみます。

//MXレコードを取得
$to = 'akatsuki@norm-nois.com';
$domain = explode('@', $to);
$domain = end($domain);
getmxrr($domain, $mx);

//ソケットを開く
$fp = stream_socket_client($mx[0].':25');

//ソケットが開なかったら処理終了
if(!$fp) {
  exit;
}

//SMTPセッションの開始
fwrite($fp, "EHLO {$mx[0]}\r\n");
$r = fread($fp, 128);

//メールの送り主を設定
fwrite($fp, "MAIL FROM: <from@hogehoge.jp>\r\n");
$r = fread($fp, 128);

//メールの送り先を設定
fwrite($fp, "RCPT TO: <".$to.">\r\n");
$r = fread($fp, 128);

//メッセージの送信開始
fwrite($fp, "DATA\r\n");
$r = fread($fp, 128);

//ステータスコードを取得
$code = substr($r, 0, 3);

//コードが250か354ならOK
if($code == 250 || $code == 354) {
  echo '王大人生存確認!!';
} else {
  echo '王大人死亡確認!!';
}

これは実際にソケット通信によってメールを送るときの手順と同じです。メールの本文を送信する直前までの手順がこれだと思ってもらって良いです。例えばCakePHPのSMTPによるメール送信のとこのプログラム(SmtpTransport.phpというファイルがどっかにある)を見ると、もう少しわちゃわちゃといろんなコードが書かれてはいますが、上記の流れでメールを送信しているのが分かります。

通常はこの処理によって正常なステータスコード(250とか354が正常なコードらしいです)が返ってきた後にメールのヘッダーや本文を送信するプログラムが走るわけですが、ここまでで処理を止めればメールは送信されませんので、ステータスコードの判定だけ行うことができるってすんぽうです。250か354が返ってくればそのメールアドレスには問題なくメールが送れるはずなので、ちゃんと実在しているメールアドレスだろうという判断ができることになります。それ以外のコードが返ってきたら存在しないメールアドレスだろうという判断になるので、送信前に弾く処理を入れることが可能です。






と、ここまで長々と語っておいてなんなのですが……。

実際のところ、このやり方はそこまで精度がよくありませんでした。僕自身、実際にこのプログラムを組んで試してみたんですが、明らかに実在することが分かっているアドレスなのにエラーが返ってきたり、逆にエラーになってくれないと困るのに正常なステータスコードが返ってきたりすることがあって、実戦で使うにはちょいリスクが高いかもしれないです。僕がテストした時は5%くらいのアドレスが正しい判定ができませんでした。5%って聞くとそこまでやばい割合じゃないような気もしますけど、でも100件中5件が間違った判定になっちゃうって思うと、何千人何万人というユーザーにメルマガを配信するようなサービスにとってはきついっすよね。この判定を入れちゃうことによって10000人のうち500人も誤判定でメールが届いてくれない可能性があるってことですからね。10000人中5人だったら、まあ……うん、って感じにならなくもないけど。

今回は25番ポートを対象にチェックをしているのですが、もしメールサーバーのポートが25番じゃないと通信エラーが発生してエラーが返ってくると思うので、その辺が原因の一つかなあとは思うんですが、すみません……はっきりとした原因は調査しきれてないです。とはいえ、MXレコードは取得できてもポート番号までは取れないからな……。

まあ、メールアドレスの存在を確認するツールは探せばいくつかあるのでそういうのに頼る方が確実かもしれないんですが、だいたいは一件ずつ手入力して確かめる感じのツールになってしまうので、もしも自前で大量のアドレスをチェックしなきゃいけないような状況にあるのなら、こういうやり方も考えられなくはないぞという程度にとどめておいてもらえばと思います。男塾の王大人も死亡確認って言ってたキャラがあとから復活するのはよくあることだったし……ね。

一応自分のサーバーにチェック用のシステムを入れられるサービスもあるんですが(↓)、設置料が10万円なので、ちょっとテストしてみたいってだけだとこの出費は躊躇してしまふ。

メールアドレス存在・生存・死活確認ツール
 もしかしたら何か関連しているかも? 
 みんなからのコメント 
2021年04月24日 17:44:35
ももんが
こーどまちがえてないかな。
fwrite($fp, "EHLO {$mx[0]}\r\n");

SMTPセッションのさいしょのごあいさつは HELO なのです。

EHLOだとこんなかんじのへんじをしてくれるよ。
EHLO localhost
250-example.com
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE 209715200
250-DSN
250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
250-STARTTLS
250-DELIVERBY
250 HELP
2021年04月24日 19:45:23
まっち~(管理人)
>ももんがさん

EHLOはHELOの拡張バージョンみたいな感じだったと思います。古いサーバーだとHELOじゃないとエラーになることもあるようですが、基本的にはEHLOでもいけるはずです。

でも確かに、EHLOを使ったせいで精度が悪かった可能性もありそうですね。HELOなら正常に応答してくれていた場合があったのかも……。

すみません。今度もうちょい検証してみます。