先日、突然EC2からのメール送信が必要ということが発覚したときの顛末を紹介します。
公開日当日に、突然以下のような内容のメールが届きました。
Dear EC2 Customer,
You recently reached a limit on the volume of email you were able to send out of SMTP port 25 on your instance:xxxxxxx
EC2内のMTAからメールを送信するプログラムでは、そのままだと上記のようなメールが届き、
メールの送信制限に引っかかります。
その為、メールの上限解除申請フォームから解除申請とメールドメインの逆引き申請を行う必要がありました。
この時点で、例えば以下の内容(メールアドレスやサーバー名などはサンプルです)で、メールを送信してみます。
# php -a
> mail("memorycraft@gmail.com", "Subject1", "Body", "From: server@memorycraft.jp");
この時点ではメールヘッダの内容は、例として以下のようになっていました。
Return-Path:
Received: from ip-10-132-10-146.localdomain (ec2-54-248-82-123.ap-northeast-1.compute.amazonaws.com. [54.248.82.123])
by mx.google.com with ESMTP id qc4si62972240pbb.326.2013.01.08.08.20.17;
Tue, 08 Jan 2013 08:20:17 -0800 (PST)
Received-SPF: neutral (google.com: 54.248.82.123 is neither permitted nor denied by best guess record for domain of root@ip-10-132-10-146.localdomain) client-ip=54.248.82.123;
Authentication-Results: mx.google.com; spf=neutral (google.com: 54.248.82.123 is neither permitted nor denied by best guess record for domain of root@ip-10-132-10-146.localdomain) smtp.mail=root@ip-10-132-10-146.localdomain
Received: by ip-10-132-10-146.localdomain (Postfix, from userid 0)
id E5D5C1E1D; Wed, 9 Jan 2013 01:20:16 +0900 (JST)
Received-SPFとAuthentication-Resultsがneutralになっているのが問題のようで、
これらがpassになっていないとメーラーによっては迷惑メールとして扱われて
しまうことがあるようです。
一応申請したものの、すぐに解除されるわけではないので、次善策としてSESを利用するのはどうかという意見が
ありました。
そこで、まずプロダクション使用申請フォームからSESのプロダクション申請を行いました。
ただし、これもすぐには通らないので、既にプロダクションモードになっている別の社内アカウントのSESを
一時的に使うことにしました。
まず、SESで送信元のメールアドレスを認証させる為に、メールアドレスを登録します。
確認メールが送られてくるのでリンクをクリックして認証します。 そうすると、以下のような画面が表示されます。
また、SESのAPIを使うようなプログラムの変更はなるべく避けたいのでメールを送信しているEC2内のpostfixから
リレーする必要があります。
ここで、suz-labブログの記事「PostfixからSESにリレー(stunnel使わない編)」が参考になりました。
この記事に従って設定していきます。
/etc/ssl/certs/ca-bundle.crtがあることを確認して、/etc/postfix/main.cfの末尾に以下を追記します。
relayhost = email-smtp.us-east-1.amazonaws.com:25
smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes
smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt
次に、sasl_passwordファイルを作ります。
SMTP用のIAMを作るためにAWS ConsoleのSESのSMTP Settingsから「Create My SMTP Credentials」ボタンで
アクセスキーとシークレットキーを取得します。
muramasaさんによると、ここではIAMで普通に作成してもダメで、SESのSMTP Settingsから作成しないと
行けないそうです。
そして、取得したアクセスキー、シークレットキーを元に、sasl_passwordファイルを作成します。
# echo "email-smtp.us-east-1.amazonaws.com:25 xxxxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyy" > /etc/postfix/sasl_password
sasl_passwordファイルを検索テーブルに設定します。
# postmap hash:/etc/postfix/sasl_passwd
再起動します。
# /etc/init.d/postfix restart
これでメールを送ってみます。
# php -a
> mail("memorycraft@gmail.com", "Subject2", "Body", "From: server@memorycraft.jp");
この場合、メールが届きません。
そこで、/var/log/mailogを見てみると、SASL認証に必要なライブラリが無いようです。
Jan 9 03:06:09 ip-10-132-134-107 postfix/smtp[11252]: warning: SASL authentication failure: No worthy mechs found
Jan 9 03:06:09 ip-10-132-134-107 postfix/smtp[11252]: 9CA2413E5: SASL authentication failed; cannot authenticate to server email-smtp.us-east-1.amazonaws.com[54.243.73.188]: no mechanism available
以下のように、ライブラリをインストールします。
# yum -y install cyrus-sasl-plain cyrus-sasl-md5
もう一度送信してみますが、メールは届きません。
maillogには以下のようになっていました。
Jan 9 04:04:51 ip-10-132-10-146 postfix/qmgr[31664]: CE9C11E25: from=, size=362, nrcpt=1 (queue active)
Jan 9 04:04:54 ip-10-132-10-146 postfix/smtp[31726]: CE9C11E25: to=, relay=email-smtp.us-east-1.amazonaws.com[23.21.84.203]:25, delay=2.5, delays=0.03/0/1.6/0.87, dsn=5.0.0, status=bounced (host email-smtp.us-east-1.amazonaws.com[23.21.84.203] said: 554 Message rejected: Email address is not verified. (in reply to end of DATA command))
Email address is not verified.となっており、メールアドレスが認証されていないようです。
これは、Fromヘッダでしか送信元を設定していない為で、Return-Path(エンベロープFrom)に
送信元メールアドレスをセットしてあげる必要があるようです。
この場合、送信コマンド側でMTAにReturnPathを渡します。
PHPのmailコマンドの場合は以下のように第4引数で-fオプションを渡します。
# php -a
> mail("memorycraft@gmail.com", "Subject30", "Body", "From: server@memorycraft.jp", "-fserver@memorycraft.jp");
そうすると正常に送信され、届いたメールのヘッダも以下のようにpassになりました。
Received-SPF: pass (google.com: domain of 0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com designates 199.255.194.183 as permitted sender) client-ip=199.255.194.183;
Authentication-Results: mx.google.com; spf=pass (google.com: domain of 0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com designates 199.255.194.183 as permitted sender) smtp.mail=0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com
Return-Path: 0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com
因みにcakePHP、FuelPHPの場合は下記のようにするようです。
・cakePHP
$config['additionalParameters'] = '-f '.$from_address;
$email = new CakeEmail();
$email->transportClass()->config($config);
$email->from(array($from_address=>'Information'))
->to($target)
->subject($mail_title)
->send($mail_message);
・FuelPHP
$email = Email::forge();
$email->from($from_address);
$email->to($target);
$email->subject($mail_title);
$email->return_path($from_address);
$email->body($mail_message);
$mail->send();
なんとか解決し、安心しました。
意外と見落としがちなメール送信ですが、いざという時、どの方法を取っても新規からではなんらかの申請が
必要になり、即時反映は難しそうでした。
こういった場合の為に、常に一時的なリレー用のSESを確保しておくのは有りかも知れません。
こちらの記事はなかの人(memorycraft)監修のもと掲載しています。
元記事は、こちら