PHP
メール送信

メール送信がうまくいかないときに読む記事(そういう質問をされたときに読ませる記事)

知恵袋teratail などのQAサイトでの頻出質問に PHP でのメール送信ができない、どうしたらいいの?ろくにソースコードを提示していないものは論外だが、この問題はソースコードだけを見たところで解決できるものではない。

送信アドレスを変更するだけで、成否が変わったり、プログラムを実行しているサーバーの設定によっても動作は変わってくる。ローカル環境から送信する場合はどこのプロバイダを使ってインターネットに接続しているかまでも影響してしまう。

質問するにしても、提示すべき情報が多いにもかかわらず、質問者からしても実際に試したソースコードをメールアドレスをさらしてまでそのまま掲載するのは心理的なハードルが高い。(本当は必要な情報なんだが…)

たまたまうまくいくことは稀なので、 この手のプログラムにはトラブルがついて回る のだが、対処するためにはDNSやSMTP、IMAPなど 幅広い知識が必要 とされる。基本的な仕組みさえ知っていれば、質問をするにしてもどんな情報を提示すべきか自ずと見えてくるはず…とのことで文章にしてみました。


質問の際に提示すべき情報


  • どこのサーバでプログラムを実行しているのか(ホスティング会社とサービスプラン、PC上のXAMPP環境など)

  • 送信者アドレス(@より後ろは必須 ex. ******@example.com)ドメイン名がわかるように

  • 宛先アドレス(@より後ろは必須 ex. ******@example.com)ドメイン名がわかるように

  • ソースコード

メール送信に関するトラブルで質問した場合、せっかく親切に回答をもらっても、基本的な知識がなければ回答の内容を理解できないと思われます。あらかじめ理解を助ける意味でも、以下の内容を把握しておきましょう。


メール送信の仕組みを知ろう

以下の図は、user@send.com から user@recieve.com へPCのメーラーからメールを送信する経路を表している。

PC


送信(SMTP)サーバ

受信(IMAP)サーバ


1.メーラーの役割

メール本文を作成し、送信ボタンを押したとき、メーラーは作成されたメールを送信するために、あらかじめ設定された送信サーバー(SMTP)の設定に従い、送信サーバーに接続を試みます。

送信サーバーの設定情報の例

* サーバー名 - mail.send.com
* メールアドレス - user@send.com
* ユーザー名 - user@send.com
* パスワード - password

設定情報は(基本的には)上記の情報となります。

メーラーは、上記の設定情報のうち、サーバー名(mail.send.com)がどこにあるのかをPCが調べます。これを 名前解決 と言います。この仕組みは電話と同じようなもので、サーバー名は「◯◯さん」にあたるもの。「電話番号」は「IPアドレス」に相当します。

サーバーへ接続するためには、 IPアドレス がないと接続できませんので、PC(OS)は電話帳に相当する「DNS」という仕組みを使ってIPアドレスを調べます。

この仕組みは Windows なら コマンドプロンプト、Mac なら ターミナル でこの仕組みを確認できます。

#windows

$ nslookup --type=mx google.com

# Mac
$ dig google.com mx

IPアドレスの取得が成功すると、送信サーバに接続のリクエストを送信します。失敗した場合は、「送信サーバが見つかりませんよー」といったエラーを表示してユーザーに伝えます。


2.送信サーバーの役割

リクエストを受けた送信サーバは「設定情報」のメールアドレス、ユーザー名、パスワードが正しいかどうかを判定し、正しければ接続を許可、間違っていれば、「ユーザー名またはパスワードが間違っています」というエラーを出して接続を切断します。

接続が成功した場合、送信サーバーがメールデータから、宛先のメールアドレスを取得し、 @ 以降のドメイン部分から、そのサーバーが存在するかどうかを判定します。先程の dig コマンドの仕組みです。

存在していれば、その受信サーバ宛にメールデータを送信します。判定に失敗した場合は、「宛先のサーバーがありませんでした」との旨を送信者宛にメールを送信します。一般的に「MAILERDAEMON 〜〜」のメールです。

なお、この段階では、受信サーバーがメールを受け取ったかどうかは検知できません。


3.受信サーバーの役割

受信サーバーは送信されてきたメールデータより「宛先メールアドレス user@recieve.com」から @ の前の部分 user が自身のサーバー内にそのユーザーが存在するかどうかを判定し、存在している場合は、user のメールボックスにデータを作成します。

ユーザーが存在していない場合は、送信してきた送信者のメールアドレス宛にエラーメールを送信します。ここでも送信されるメールは「MAILERDAEMON...」のメールです。

基本的にはこういった動きになるのですが、実際には全てのメールを受け取るわけではありません。recieve.com というサーバーが存在することは公知のことですので、@より前の部分をランダムに生成して、「存在するかもしれないメールアドレス」に対してメールを送ることは容易にできてしまいます。「スパムメール」のおおくが、自動化されたプログラムで送ってくるので、その数も膨大な数になってしまいます。

そこで、受信サーバーはスパムの可能性が高いものをはじめから拒否するような仕組みを持っているのです。


  • 送信者のアドレスのドメイン部分が、送信サーバーのドメインと一致しているか

  • 送信してきたサーバーがブラックリストに登録されていないか

  • 送信してきたサーバーが、大量に連続してメールを送信してきてはいないか

  • 送信してきたサーバーが、存在していないユーザーに送信してきてはいないか

などを判定して、スパムを弾きます。


PHPコードで見るメール送信の動き


mail(mb_send_mail)関数を使った例

<?php

//言語設定、内部エンコーディングを指定する
mb_language("japanese");
mb_internal_encoding("UTF-8");

//日本語メール送信
$to = "user@recieve.com";
$subject = "例の件について";
$body = "どうでしょう?";
$from = "user@send.com";
mb_send_mail($to,$subject,$body,"From:".$from);

この例だと、送信サーバーを認証することなく直接受信サーバーにメールを送りつけます。受信サーバーが正常にメールを受け付ける可能性が低いことは自明かと思います。


PHPMailerを使った例

<?php

require_once("./phpmailer/class.phpmailer.php");
mb_internal_encoding("UTF-8");

$to = "user@recieve.com"; //宛先
$subject = "メールの件名"; //件名
$body = "メールの本文です。"; //本文
$from = "user@send.com"; //差出人
$fromname = "送信者"; //差し出し人名

$mail = new PHPMailer();
$mail->CharSet = "iso-2022-jp";
$mail->Encoding = "7bit";

$mail->IsSMTP(); //「SMTPサーバーを使うよ」設定
$mail->SMTPAuth = TRUE; //「SMTP認証を使うよ」設定
$mail->Host = 'send.com:25'; // SMTPサーバーアドレス:ポート番号
$mail->Username = 'user'; // SMTP認証用のユーザーID
$mail->Password = 'password'; // SMTP認証用のパスワード

$mail->AddAddress($to);
$mail->From = $from;
$mail->FromName = mb_encode_mimeheader(mb_convert_encoding($fromname,"JIS","UTF-8"));
$mail->Subject = mb_encode_mimeheader(mb_convert_encoding($subject,"JIS","UTF-8"));
$mail->Body = mb_convert_encoding($body,"JIS","UTF-8");

//メールを送信
$mail->Send();

PHPMailerのような高機能なライブラリには、SMTPサーバーを中継したメールを送信することが、比較的簡単にコーディング出来るようになっています。正規の送信サーバーを中継しているので、メールの到達の可能性が高いことがわかりますね。

※送信アドレスが @gmail.com の場合、うまくいかないことがあります。

gmailのSMTPについては、送信するアプリケーションを判定し、「最新のセキュリティ標準を満たしていないアプリからあなたの Google アカウント(********@gmail.com)にログインしようとした人がいます。」として警告し、接続を拒否しますので、送信アドレスとしては利用しないことを勧めます。

PHPMailerを使ってGmailのSMTP経由でメールを送信する際にSMTP connect() failed.と出て困った

スクリーンショット 2016-01-13 17.14.18.png

スパムの送信アドレスに利用されることが多いための措置かと…


Gmailを使ったサンプルコード

<?php

ini_set('display_errors', true);
error_reporting(E_ALL);

require 'vendor/autoload.php';

use PHPMailer\PHPMailer\PHPMailer;

// コンストラクタ
$mail = new PHPMailer();

// 文字コード
$mail->CharSet = "iso-2022-jp";
$mail->Encoding = "7bit";

// SMTPサーバーを利用する
$mail->IsSMTP();

// デバッグ
$mail->SMTPDebug = 1;

// SMTPAuthを利用する
$mail->SMTPAuth = true;

// SMTPサーバー
$mail->Host = 'smtp.gmail.com';

// ユーザー名
$mail->Username = 'xxxxxxxx@gmail.com';

// パスワード
$mail->Password = 'password';

// ポート
$mail->Port = 587;

// メールヘッダー文字コード
$mimeheader_encoding = 'JIS';

// 送信者アドレス
$mail->From = 'xxxxxxxx@gmail.com';

// 送信者名
$mail->FromName = mb_encode_mimeheader(
'送信者名'
, $mimeheader_encoding
);

// 宛先
$mail->addAddress('user@example.com');

// メールタイトル
$mail->Subject = mb_encode_mimeheader(
'メールタイトル'
, $mimeheader_encoding
);

// メール本文
$mail->Body = mb_convert_encoding(
'本文'
, $mimeheader_encoding
);

// 添付ファイル
// マルチバイト文字を利用するときは、mb_encode_mimeheader() を使う
$mail->addAttachment(
'./test.zip'
, mb_encode_mimeheader(
'添付ファイル.zip'
, $mimeheader_encoding
)
);

if (!$mail->send()) {
echo $mail->ErrorInfo;
}


記事の不備について

できるだけ、初心者プログラマにもわかるようにと思って記述しましたが、「こういう表現のほうがいいんじゃない?」というようなアイディアがありましたら、遠慮なくご指摘ください。