SendGridのX-SMTPAPIヘッダの使い方(Section Tags、Substitution Tags編)

前回に引き続きSendGridについてです。
今回はSMTP APIを使ってみました。


SMTP APIについて

SendGridのSMTP APIは基本的には普通のSMTPプロトコルに則ったものですが、X-SMTPAPIヘッダというカスタムヘッダを使うことで機能を拡張することができます。


X-SMTPAPIヘッダについて

X-SMTPAPIヘッダはSMTPプロトコルの拡張ヘッダです。

X-SMTPAPI: { "category": "newuser" }

みたいな感じでJSON形式でパラメータを与えることで機能を拡張します。


Section TagsとSubstitution Tagsについて

今回はSMTP APIで使える機能のうちSection TagsとSubstitution Tagsを使ってみました。
Section TagsとSubsitution Tagsは送信メールの内容を宛先毎に特定の文字列を置換するための機能です。toと合わせて使うことで機能します。
例えば、X-SMTPAPIヘッダの値を以下のような形にします。

{
    "to": ["test1@xxx.xxx", "test2@yyy.yyy"],
    "sub": {
        "-body-": [ "-bodyFemale-", "-bodyMale-" ],
        "-favorite-": [ "バナナ", "カツオ" ], "
        "-nickname-": [ "ミキ", "サブロー" ] },
    "section": { "
    "-bodyFemale-": "-nickname-さん!女性向けの商品のご紹介です。",
    "-bodyMale": "-nickname-さん!男性向けの商品のご紹介です。" }
}

後述しますが、SMTPヘッダ上に日本語をそのまま乗せることができないため、日本語を扱う場合はRFC1522に従って適切にエンコードする必要があります。

上記例では、toに二つのアドレスを指定しています。これにより、このメールが二つのアドレス宛に配信されます。SMTPプロトコルのtoヘッダと同じく宛先アドレスを指定するためのものですが、X-SMTPAPIのtoを使うと、宛先毎に置換文字列を変更することができます。尚、X-SMTPAPIのtoを指定すると、SMTPプロトコルのtoヘッダで指定したアドレスにはメールは送信されません。

その次のsubとsectionはそれぞれ文字列の置換設定です。
subは、宛先アドレス毎の置換設定を行います。置換キー(上記例では-boty-や-favorite-)に対応した置換文字列を宛先アドレス毎に配列で与えます。宛先毎の単純な置換であればtoとsubのみで置換が実現できます。

sectionは、sub内で定義された置換キーをさらに置換するための設定です。
subだけだと宛先毎に置換設定を全て与える必要がありますが、sectionを組み合わせて使用することにより置換設定を集約することができます。


Javaによる実装例

以下にJavaによる実装例を示します。
認証情報であるSMTP_AUTH_USERはSMTP_AUTH_PWDにはSendGridのアカウント情報を与えます。

    public void testSMTPAPI() throws MessagingException {
        Properties props = new Properties();
        props.put("mail.transport.protocol", "smtp");
        props.put("mail.smtp.host", "smtp.sendgrid.net");
        props.put("mail.smtp.port", 2525);
        props.put("mail.smtp.auth", "true");
        
        Authenticator auth = new SMTPAuthenticator();
        Session mailSession = Session.getDefaultInstance(props, auth);
        mailSession.setDebug(true);
        Transport transport = mailSession.getTransport();
        MimeMessage message = new MimeMessage(mailSession);
        
        // X-SMTPAPIヘッダ値
        String smtpApiHeaderValue =  
                "{" +
                        "\"category\": [\"category1\", \"category2\" ]," +
                        "\"to\": [\"test1@xxx.xxx\", \"test2@yyy.yyy\"]," +
                        "\"sub\": { " +
                                "\"-body-\": [ \"-bodyFemale-\", \"-bodyMale-\" ], " +
                                "\"-favorite-\": [ \"バナナ\", \"カツオ\" ], " +
                                "\"-nickname-\": [ \"ミキ\", \"サブロー\" ] }, " +
                        "\"section\": { " +
                                "\"-bodyFemale-\": \"-nickname-さん!女性向けの商品のご紹介です。\", " +
                                "\"-bodyMale\": \"-nickname-さん!男性向けの商品のご紹介です。\" }" +
                  "}";
        // X-SMTPAPIヘッダ値をRFC1522に従ったエンコード(ISO-2022-JP+Base64でエンコード)
        String encSmtpApiHeaderValue = encodeRfc1522(smtpApiHeaderValue);
        // X-SMTPAPIヘッダ追加
        message.setHeader("X-SMTPAPI", encSmtpApiHeaderValue);

        Multipart multipart = new MimeMultipart("alternative");
        
        BodyPart part1 = new MimeBodyPart();
        part1.setText("ようこそ!\n -body- ");
        
        BodyPart part2 = new MimeBodyPart();
        part2.setContent("ようこそ!\n -body- ", "text/html;charset=iso-2022-jp");
        
        multipart.addBodyPart(part1);
        multipart.addBodyPart(part2);
        
        message.setContent(multipart);
        message.setFrom(new InternetAddress("awwa500@gmail.com"));
        message.setSubject("ようこそ-favorite-好きの-nickname-さん!");
        message.addRecipient(Message.RecipientType.TO,
                new InternetAddress("awwa500@gmail.com"));
        
        transport.connect();
        transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
        transport.close();
    }
    
    /**
     * RFC1522に従ったエンコードを行う。
     * @param header
     * @return
     */
    private String encodeRfc1522(String header) {
        byte[] enc = unicode2iso2022jp(header);
        String base64 = Base64.encodeBase64String(enc);
        String ret = String.format("=?ISO-2022-JP?B?%s?=", base64);
        return ret;
    }
    
    /**
     * UnicodeをISO-2022-JPに変換する
     * @param unicode
     * @return
     */
    private byte[] unicode2iso2022jp(String unicode) {
        CharsetEncoder jisEncoder = Charset.forName("ISO-2022-JP").newEncoder();
        ByteBuffer encoded = null;
        byte[] capacity = null;
        List< byte > temp = new ArrayList< byte >();
        byte[] result = null;

        //(Windows-31J -> Unicode) -> (ISO-2022-JP -> Unicode)
        unicode = unicode.replaceAll("\uff5e", "\u301c"); //~
        unicode = unicode.replaceAll("\uff0d", "\u2212"); //-

        try {
            encoded = jisEncoder.encode(CharBuffer.wrap(unicode.toCharArray()));
            capacity = encoded.array();
        } catch (CharacterCodingException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < encoded.limit(); i++) {
            temp.add(capacity[i]);
        }

        result = new byte[temp.size()];
        
        for (int i = 0; i < result.length; i++) {
            result[i] = temp.get(i);
        }

        return result;
    }
    
    private class SMTPAuthenticator extends javax.mail.Authenticator { 
        public PasswordAuthentication getPasswordAuthentication() {
            String username = SMTP_AUTH_USER;
            String password = SMTP_AUTH_PWD;
            return new PasswordAuthentication(username, password);
        }
    }

X-SMTPAPIヘッダに日本語を含める場合、JSON形式のX-SMTPAPIヘッダの値をISO-2022-JPにエンコード→Base64でエンコード→=?ISO-2022-JP?B?と?=で挟み、X-SMTPAPIヘッダの値とします。詳しくはMIMEメッセージ・ヘッダを参照してください。

尚、本実装例では、メール送信にJavaMailを、Base64エンコードにApache Commons Codecを使用しています。それぞれダウンロードしてJava Build Path設定する必要があります。
UnicodeからISO-2022-JPへの変換はここのコードを参考にさせていただきました。

実際のSMTPログ

上記コードを実行した際にSMTPレベルでは以下のような形で送信されます。

MAIL FROM:< awwa500 gmail.com="" >
250 Sender address accepted
RCPT TO:< awwa500 gmail.com="" >
250 Recipient address accepted
DEBUG SMTP: Verified Addresses
DEBUG SMTP:   awwa500@gmail.com
DATA
354 Continue
From: awwa500@gmail.com
To: awwa500@gmail.com
Message-ID: < 857595140 .1.1381585937500.javamail.awwa="" awa-no-macbook-air.local="" >
Subject: =?UTF-8?Q?=E3=82=88=E3=81=86=E3=81=93=E3=81=9D-favorite-=E5=A5=BD?=
 =?UTF-8?Q?=E3=81=8D=E3=81=AE-nickname-=E3=81=95=E3=82=93=EF=BC=81?=
MIME-Version: 1.0
Content-Type: multipart/alternative; 
 boundary="----=_Part_0_1074010510.1381585935768"
X-SMTPAPI: =?ISO-2022-JP?B?eyJjYXRlZ29yeSI6IFsiY2F0ZWdvcnkxIiwgImNhdGVnb3J5MiIgXSwidG8iOiBbInRlc3QxQHh4eC54eHgiLCAidGVzdDJAeXl5Lnl5eSJdLCJzdWIiOiB7ICItYm9keS0iOiBbICItYm9keUZlbWFsZS0iLCAiLWJvZHlNYWxlLSIgXSwgIi1mYXZvcml0ZS0iOiBbICIbJEIlUCVKJUobKEIiLCAiGyRCJSslRCUqGyhCIiBdLCAiLW5pY2tuYW1lLSI6IFsgIhskQiVfJS0bKEIiLCAiGyRCJTUlViVtITwbKEIiIF0gfSwgInNlY3Rpb24iOiB7ICItYm9keUZlbWFsZS0iOiAiLW5pY2tuYW1lLRskQiQ1JHMhKj13QC04fiQxJE4+JklKJE4kND5SMnAkRyQ5ISMbKEIiLCAiLWJvZHlNYWxlIjogIi1uaWNrbmFtZS0bJEIkNSRzISpDS0AtOH4kMSROPiZJSiROJDQ+UjJwJEckOSEjGyhCIiB9fQ==?=

------=_Part_0_1074010510.1381585935768
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64

44KI44GG44GT44Gd77yBCiAtYm9keS0g
------=_Part_0_1074010510.1381585935768
Content-Type: text/html;charset=iso-2022-jp
Content-Transfer-Encoding: quoted-printable

=1B$B$h$&$3$=3D!*=1B(B
 -body- 
------=_Part_0_1074010510.1381585935768--
.
250 Delivery in progress
QUIT
221 See you later


結果

こんな感じでそれぞれのアドレスにメールが届きました。



制限事項

通常のSMTPプロトコルと同じ制限事項を守る必要があります。
特に、ヘッダは1行あたり72文字以内、本文は1行あたり1000文字以内などが引っかかりやすい点です。
詳しくはこちらを参照してください。

その他

指定したJSON形式に問題がある場合、配信に失敗(Drop)します。
配信に失敗した場合、SendGridのEmail Activity上に「Drop」表示されます。





SendGridのアカウントに登録したアドレスにもDropされた旨通知メールが届きます。これがないとデバッグできませんね。



コメント

このブログの人気の投稿

kintone-sendgrid-pluginを更新しました(v11)

Google Apps Device Policyはじめました(冷やし中華風に)