code up

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

AWS SESを使ったメール送信あれこれ

諸事情によりJavaとphpの両方の疎通確認。

Java

Javaの場合、選択肢はふたつ・・・かな?

ひとつはAWS SDK for Javaを使う方法、もうひとつはJavaMailを使う方法。

AWS外でWebアプリケーションの開発、デプロイ先がEC2上のTomcatという構成のためJavaMailで送信することに。

JavaMailで送信する場合は、SMTPで送信するということになるため事前にSES Management Console - SMTP SettingsからCredentials(ユーザー名とパスワード)をCreate My SMTP Credentialsボタンから作成しておく、この時表示されるパスワードは以後確認することができなくなるので注意。

次にSESのSTMPでどう認証すればいいのかを調べる。SES Management Console - SMTP Settingsには『When you connect to the Amazon SES SMTP interface, you will need to supply your SMTP credentials - a user name and a password.』とある。次にサポートされているアルゴリズムを調べる。非セキュアな方のSMTPインタフェースに接続して、EHLO hostnameと打つ。

EHLO localhost
250-email-smtp.amazonaws.com
250-8BITMIME
250-SIZE 10485760
250-STARTTLS
250-AUTH PLAIN LOGIN
250 Ok

SMTP-AUTHだけどCRAM-MD5やDIGEST-MD5はサポートされていない模様。その代わりSES Management Console - SMTP Settingsには『Use Transport Layer Security (TLS): Yes』とあるので、SSL/TLS(この場合はポート465)、STARTTLS(587)を使って通信路を暗号化するように!ということらしい。POP before SMTPは不要(そもそもPOPサーバーがない)。

JavaMailはDIGEST-MD5はサポートしているもののCRAM-MD5はサポートしていない(GNU JavaMailがサポートしているらしいが未確認)。SSL/TLS、STARTTLSはどちらもJavaMailでサポートされているが今回は465ポートへのSSL/TLS接続で送信した。

import java.util.Properties;

import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

public class Mails {

	public static void main(String[] args) throws Exception {

		String charset = "iso-2022-jp";
		Store store = null;

		try {
			Properties props = new Properties();
			Session session = null;

			props.setProperty("mail.transport.protocol", "smtps");
			props.setProperty("mail.smtps.host", "email-smtp.us-east-1.amazonaws.com");
			props.setProperty("mail.smtps.port", "465");

			// mail.smtps.fromのアドレスがReturn-Pathにセットされる
			props.setProperty("mail.smtps.from", "return-path@foo.bar");

			props.setProperty("mail.smtps.auth", "true");

			// DIGEST-MD5認証は現在サポートされてないが今後サポートされた場合に備えて指定
			props.setProperty("mail.smtps.auth.mechanisms", "DIGEST-MD5 LOGIN PLAIN");

			session = Session.getDefaultInstance(props);

			MimeMessage message = new MimeMessage(session);

			message.setFrom(InternetAddress.parse("from@foo.bar")[0]);

			message.setSubject("件名", charset);
			message.addRecipients(Message.RecipientType.TO,
				InternetAddress.parse("to@foo.bar"));

			Multipart multipart = null;
			MimeBodyPart messageBodyPart = null;

			messageBodyPart = new MimeBodyPart();
			messageBodyPart.setText("本文", charset);

			multipart = new MimeMultipart();
			multipart.addBodyPart(messageBodyPart);

			message.setContent(multipart);

			Transport transport = session.getTransport();

			// Credentialsの情報を指定
			transport.connect("ユーザー名", "パスワード");
			transport.sendMessage(message, message.getAllRecipients());
		} finally {
			if (store != null && store.isConnected()) {
				store.close();
			}
		}
	}
}

注意点など

  • 上記サンプルコードでは、¬や~などの文字化けの対応はしていない。テストする際や変換テーブルを作る場合は、もう存在しないページのアーカイブだけどhttp://www.ingrid.org/java/i18n/unicode.htmlが役立つ
  • Amazon SESではメールの品質の基準としてバウンスメール・苦情の率がある。そのためだと思うがメール送信時にReturn-Pathが@amazonses.comのメールアドレスに書き換えられる。エラー時には一旦Amazon SESにそのメールを戻して、バウンスメールを記録してから元々のReturn-Pathに戻ってくる。従ってテストする時はヘッダーを見るだけでは確認できず、実際にエラーとなるメールアドレス(存在しないドメイン、存在しないユーザー、メールボックス満杯など)に送らないと確認できない。しかも、しっかりAmazon SESのログにはバウンスメールとして残るのでバウンスメールのテスト送信には注意が必要
  • Return-PathとFromのアドレスはVerified Sendersに登録されたものでないと送信に失敗する
  • Developer GuideによるとSTARTTLSの場合(だけ?)はサーバー証明書の検証を行うことを推奨している。別途SSLSocket#startHandshakeでVerifyできたが、JavaMailのAPIだけでは検証できないかも

php

phpの場合の選択肢は公式サンプル (mail()関数; php→SMTP[sendmail/Postfix])mb_send_mailPEAR::MailSimpleEmailService (ses.php)AmazonSESMailer.phpPHPMailerAWS SDK for PHP (AmazonSES#send_email)などなどいっぱい。

PHPの開発はサーバー上で直接行うため、また認証情報を送信処理を記述するphpに直接ハードコードしなくて済むAWS SDK for PHPを使用した。選択肢のうち、動作を確認したのはこれ(AWS SDK for PHP (AmazonSES#send_email))のみ。

いくつか記事を確認したが、AmazonSES()のコンストラクタにアクセスキーIDやシークレットアクセスキーを渡す例しかなくBackwards-Incompatible Changes!として仕様が変更になった後の例はあまり見つからなかった。

Apache, PHP, AWSSDKforPHPはインストール済み以後のEC2上での作業。rootで行った。

# cd /usr/share/pear/AWSSDKforPHP/
# cp config-sample.inc.php config.inc.php

# vi config.inc.php

とサンプル設定ファイル(config-sample.ini.php)をconfig.ini.phpとコピーして次の部分を修正。

	'key' => '[アクセスキーID]',
	'secret' => '[シークレットアクセスキー]',

ここで一旦HTTP Serverを再起動。そしてメール送信を行うphpの作成。

# service httpd restart

# vi /var/www/html/ses.php
<?php
require_once 'AWSSDKforPHP/sdk.class.php';
require_once 'AWSSDKforPHP/services/ses.class.php';

$ses = new AmazonSES();
$res = $ses->send_email(
	'from@foo.bar',
	array(
		'ToAddresses' => array('to@foo.bar')
	),
	array(
		'Subject' => array(
			'Data' => "件名",
			'Charset' => 'ISO-2022-JP'
		),
		'Body' => array(
			'Text' => array('Data' => "本文\n",
			'Charset' => 'ISO-2022-JP')
		)
	),
	array(
		'ReturnPath' => 'return-path@foo.bar'
	)
);
echo $res->isOK() ? "OK": "NG";
?>

注意点など

  • 本文の最後が日本語の場合、Thunderbirdで受信したメールを見ると文字化けを表す記号が表示されてしまうので改行コードで終わらせている
  • 文字化けの調査はしてないので上記サンプルコードだけだと文字化けするかも
  • バウンスメール(Return-Path)とVerified Senders(From, Return-Path)の注意点はJavaと一緒

SDK使うとだいぶ楽だってことが分かった・・・・!

関連記事
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。