デス/ブラックメタル専門の佐藤です。最近はBAND-MAIDにハマっています。共通しているのは「白黒」ですかね。
この記事はSendGrid Advent Calendar 2016の3日目の投稿です。
今回はGmailの挙動に関連した話題です。一部、公式情報が見つからず確認ベースの情報も含まれているので、あくまでも現時点で確認した挙動という前提で読んでいただけると幸いです。試す場合は自己責任でお願いします。
課題
SendGridでは、SMTP APIというメール送信をカスタマイズする機能があります。この機能を利用すると、1回のリクエストで最大1万アドレス宛に個別送信でき、送信リクエストにかかる時間を大幅に短縮できます。しかし、この機能をテストするため、Gmailのエイリアス宛にメール送信してみると、何故か1通しか届いていない、という現象に出くわすことがあります。
調査1
パッと思いつくのは次の状況です。
- SendGridがエイリアス宛のメールを破棄して送っていない
- Gmailが受け取ったメールを表示していない
こんなときはまずSendGridのActivityを確認します。Delivered
イベントがエイリアスアドレスごとに発生しているのでSendGridが指定したアドレスそれぞれに送信していることが確認できます。というわけで、考えられるのは「2. Gmailが受け取ったメールを表示していない」となります。
調査2
実はこの現象、Gmail単独で再現させることができます。やり方は、Gmailで次のように自分のエイリアスアドレスを複数指定してメールを送信するだけです。
To: hoge+a@gmail.com, hoge+b@gmail.com
2通届くかと思いきや、1通しか届きません。どうやらGmailでは同じメールと判断されるものは1通しか表示しないようになっているようです1。
対策
Gmailで送る場合の挙動(調査2)はどうしようもありませんが、SendGridで送る場合の挙動については、送信方法を工夫することで回避できます。色々条件を変えて試してみたところ、「同一メールボックス内のMessage-IDおよびメールの件名が同じメール」は重複メールとみなされ1通しか表示されていないようです1。Message-IDまたは件名をエイリアスアドレス毎に変えてやればこの現象を回避できるはずです。以下、いくつかの方法をご紹介します。
その1:リクエストを分ける
最も単純な方法は、SendGridに対する送信リクエストを宛先毎に別々にすることです。SendGridでは、送信リクエスト単位でMessage-IDを自動生成するので、エイリアスアドレス毎に送信リクエストを繰り返すことで上述の条件を回避できます。ただ、当然ながらこの方法ではSMTP APIの「1回のリクエストで最大1万アドレス宛にメール送信できる」機能の恩恵を受けることはできません。
その2:Substitution Tagを利用して宛先毎に件名を変える
次に考えられるのは、SMTP APIのSubstitution Tagを利用して件名を変える方法です。宛先毎に件名を変えてやれば、上述の条件を回避できます。Substitution Tagを件名に追加してやる必要があるというのが難点ですが、SMTPまたはWeb API v2を利用した場合はこの方法が最善策ではないかと考えています。
その3:フォーマット不正なMessage-IDを指定する
ご覧の通り胡散臭い回避方法です。推奨しません。
SMTPやWeb API v2では、送信側で拡張ヘッダを指定できます。ここでMessage-IDヘッダを指定してやります。ただし、拡張ヘッダでは上述のSubstitution Tagが効かないので、宛先毎にMessage-IDヘッダを変える技が使えません。そこで、意図的にフォーマット不正なMessage-IDヘッダを指定してやります。
Web API v2 Mail Sendだとheadersパラメータに以下のような値を設定します。
{"Message-ID": "unko"}
この状態で普通にSMTP APIを使って複数のエイリアスアドレス宛に送信すると、Gmailはご丁寧にフォーマット不正なMessage-IDを自動生成&訂正してくれます1(メールヘッダで確認可能)。で、めでたく別々のメールとして認識されます。Gmail奥が深い。
Message-ID: <******.***.*****.****SMTPIN_ADDED_BROKEN@mx.google.com>
X-Google-Original-Message-ID: unko
その4:Web API v3でMessage-IDヘッダを指定する
最後の方法は、Web API v3 Mail Sendエンドポイント限定の回避方法です。Web API v3では、Personalizations配列の要素毎に宛先、件名、ヘッダなど様々な条件を指定できます。この内、headers
パラメータでMessage-IDヘッダを指定することでメールの表示上余計な変更を加えることなく上述の条件を回避できます。Web API v3を利用できるのであれば、この方法が最もスマートな方法でしょう。
{
"personalizations": [
{
"to": [{"email": "hoge+a@gmail.com"}],
"headers": {"Message-ID": "<hoge1@example.com>"}
},
{
"to": [{"email": "hoge+b@gmail.com"}],
"headers": {"Message-ID": "<hoge2@example.com>"}
}
],
...
}
Message-IDはメッセージを一意に識別するIDです。自身で設定する場合は、RFC5322に基いて責任を持って一意なIDを設定してください。ちなみに、SendGridが自動生成するMessage-IDは次のようになっています。
Message-ID: <****@****.sendgrid.net>
さいごに
多少手荒な方法含めご紹介しましたが、何かの役に立つとうれしいです。
SendGridは指定された内容のメールを素直に送るので、実在する宛先に大量送信する場合は、宛先の受信制限に気を付けてください。大量送信のテストを行う場合は「sink.sendgrid.net」ドメイン宛に送る方法もあります。