[AWS][Java] Amazon SESで送信者名を表示させる

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。こむろです。
今年もJavaJavaしていきたいと思います。よろしくお願いします。

2015年初めの自分の実行環境は以下のとおりです。

  • Mac OSX 10.9.5
  • SBT launcher version 0.13.5
  • java version "1.8.0_11"

AWS SDK for Javaを使ってメールを送信する

まずはメール送信を行うサンプルコードです。

SendEmailRequest request = new SendEmailRequest();

String title = "タイトル";
String message = "メッセージ";
String fromAddress = "komuro.hiraku@classmethod.jp";   // 自分のメールアドレスはVerify済み
String toAddress = "komuro.hiraku@classmethod.jp";

request.withDestination(new Destination()
        // 送信先アドレスを追加
        .withToAddresses(toAddress))
        // 送信元アドレスを追加
        .withSource(fromAddress)
        // メッセージを追加.
        .withMessage(createMessage(title, message));

client.sendEmail(request);

createMessage(String, String)は以下の通り定義してあります。

/**
 * メッセージを作成する
 * @param title メール件名
 * @param message メール本文
 * @return {@link com.amazonaws.services.simpleemail.model.Message}
 */
private Message createMessage(String title, String message) {
    // Titleを作成
    Content subject = new Content(title);
    Content body = new Content(message);
    return new Message(subject, new Body(body));
}

実にシンプルです。これでメールが送信できるのだから世の中楽になったものです。
さてこれを実行すると、メールが送信されてきます。

スクリーンショット 2015-01-22 0.33.55

設定したタイトル、メッセージなどがきちんと表示されていることが分かります。

送信者名を表示したい

さて、問題の「送信者名を表示する」を実装します。
送信者名を表示というのは具体的には以下のような表示を指しています。

スクリーンショット 2015-01-22 0.38.38

大変見慣れた表記ですね。

SendEmailRequest#withSource(String)は、単に文字列を要求しているので、単純に文字列を渡せば良いのでしょうか。
以下のように記述してみます。

SendEmailRequest request = new SendEmailRequest();

String title = "タイトル";
String message = "メッセージ";
String fromAddress = "こむろ <komuro.hiraku@classmethod.jp>";   // 送信者名をくっつけた
String toAddress = "komuro.hiraku@classmethod.jp";

request.withDestination(new Destination()
        // 送信先アドレスを追加
        .withToAddresses(toAddress))
        // 送信元アドレスを追加
        .withSource(fromAddress)
        // メッセージを追加.
        .withMessage(createMessage(title, message));

client.sendEmail(request);

これで問題ないでしょう。実行してみます。

スクリーンショット 2015-01-22 0.43.02

はい。見事に文字化けしました。

それもそのはず、メールの送信者指定の表記はRFCできちんと定義されています。当然上記のSendEmailRequest#withSourceのJavaDocでもしっかりと記述されていますね。ちゃんと読みましょう(自戒)

By default, the string must be 7-bit ASCII. If the text must contain any other characters, then you must use MIME encoded-word syntax (RFC 2047) instead of a literal string. MIME encoded-word syntax uses the following form: =?charset?encoding?encoded-text?=. For more information, see RFC 2047.

条件は2つ。7-bitのASCIIで記述された文字列であることRFC 2047に準拠していること

文字化けを直す

さてどうしたらよいでしょう。Javaには昔からあるJavaMailというライブラリがあります。こちらを利用しましょう。
自分はplay frameworkを利用しているので、build.sbtに以下のように追記します。Gradleの場合などは適宜読み替えてください。

libraryDependencies ++= Seq(
  "com.amazonaws" % "aws-java-sdk" % "1.8.9",
  "javax.mail" % "mail" % "1.4.7"
)

さて、準備は出来たので修正しましょう。

アプローチ1. JavaMailのMimeUtilityでEncodeする

結論からいうとこちらは不正解。正常な動作に辿りつけず。

MimeUtilityというクラスが存在してるのを見つけました。その中にencodeText(String)というメソッドがあったので、こちらを採用してみました。JavaDocを見る限りも以下のような表現があったので、いけるのではないかと考えました。

Encode a RFC 822 "text" token into mail-safe form as per RFC 2047.

RFC 822が気になったので少し調べてみると、どうも文法や表記についての記述があります。そこで以下のようなメソッドを用意して修正してみました。

private String senderAddressBuilder(String fromAddress, String senderName) {
    StringBuilder builder = new StringBuilder(senderName);
    builder.append(" <");
    builder.append(fromAddress);
    builder.append(">");
    try {
        return MimeUtility.encodeText(builder.toString());
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return fromAddress;
}

これを呼び出してアドレス表記を正しいものへ変換してみます。

SendEmailRequest request = new SendEmailRequest();

String title = "タイトル";
String message = "メッセージ";
String senderName = "こむろ";
String fromAddress = "komuro.hiraku@classmethod.jp";
String toAddress = "komuro.hiraku@classmethod.jp";

request.withDestination(new Destination()
        // 送信先アドレスを追加
        .withToAddresses(toAddress))
        // 送信元アドレスを追加
        .withSource(senderAddressBuilder(fromAddress, senderName))
        // メッセージを追加.
        .withMessage(createMessage(title, message));

client.sendEmail(request);

こちらを実行した結果が以下になります。

スクリーンショット 2015-01-22 1.13.53

これはヒドイ。このアプローチはダメなようですね。別の方法を考えましょう。

アプローチ2. JavaMailのInternetAddressを作成する

結論からいうとこちらが正解です。InternetAddressを利用することで、RFC822に準拠した形にしてくれます。詳細はこちら。

private String senderAddressBuilder(String fromAddress, String senderName) {
    try {
        InternetAddress address = new InternetAddress(fromAddress,
                senderName, "ISO-2022-JP");
        return address.toString();
    } catch (UnsupportedEncodingException e) {
        Logger.warn("Internet Address Constructor throws UnsupportedEncoding.", e);
    }
    return fromAddress;
}

素晴らしいことにtoString()の説明を見てみると以下のように記述してあります。

Convert this address into a RFC 822 / RFC 2047 encoded address. The resulting string contains only US-ASCII characters, and hence is mail-safe.

ということで、このクラスを使えばインスタンスの生成時の引数にメールアドレス送信者名を指定し、出来たインスタンスのtoString()を呼び出してあげるだけで簡単にRFCに準拠した形に変換してくれるようです。今までの苦労はなんだったんだ!

スクリーンショット 2015-01-30 21.32.38

ちょっと予想と違う形になってますが、これは自分のアドレスがアドレス帳に登録されてるからでしょうか。うーん。

ま、まあ、当初の目的とした「送信者名」を表示するという目的は達成出来たかと思います!

まとめ

AWSのSESは簡単にサクッとメールを送る時は大変有用なのですが、ちょっとだけ凝ったことをしようと思うと、色々と罠が仕掛けられていました。メール送信の要件の時には注意が必要です。

まだまだ現役で使えます。JavaMail。ありがとうJavaMail!

参照