MIXI DEVELOPERS NOTE
📫

SendGridで大量のメールを送信する際のTIPS

2022/12/14に公開

この記事はMIXIアドベントカレンダー2022の14日目の記事です。

この記事について

SendGridは多くのシステムで利用されているメール配信サービスです。大量のメールを送信することも可能なサービスですが、適切な機能を使わないと効率が悪くなってしまうことがあります。この記事ではSendGridのAPIを利用して効率よくメール送信する際のポイントや注意点をまとめて記載します。

前置き

SendGridの日本代理店である構造計画研究所から「SendGridを使って短時間に大量のメールを送るための方法」というブログが公開されており、本記事の内容はそちらのブログを参考にして執筆しています。本記事ではSendGridのAPIに対象を絞った上で、なぜその機能を使うと嬉しいのかというポイントも解説していきます。

想定読者

SendGridを使い始めた人、SendGridのメール送信パフォーマンスを改善したい人を想定しています。

TIPS

Web API vs SMTP API

SendGridのAPIには Web APISMTP APIがあります。プログラム上からSendGridでメール送信したい場合、基本的にどちらかを選択することになります。どちらでも大量のメール送信をすることは可能ですが、SMTPはプロトコルの都合上、SendGridと通信する際に通信のオーバーヘッドがあるので Web APIを使う方が推奨されているようです。

※ このSMTPのオーバーヘッドは、1度確立したコネクションを使い回すことで軽減できるようです。参考

(追記)
実際に私が関わっているプロジェクトでSMTP APIを用いたメール送信をおこなっていたのですが、複数の宛先のメール送信が不安定になる問題が発生しました。新しく実装するのであればWeb APIを利用することをお勧めします。
(追記ここまで)

それぞれ通信方法が異なるため、認証の仕方やカスタマイズの方法も異なってきます。SendGridのドキュメントや他のブログ記事などを参照する際は、どちらの方式を前提にして書かれているのか注意する必要があります。ほとんどの機能はどちらのAPIでも利用可能なので、例えば Web APIを前提にして書かれている記事でも、同等の機能が SMTP APIでも利用可能である可能性は高いです。

この記事のTIPSも両方のAPIで役に立つものです。(具体例についてはSMTPを記載します)

効率的にメール送信する

1回のリクエストで複数の宛先へメールを送信する

複数の宛先へメールを送信する際、宛先ごとにリクエストを送信するとその度にSendGridと通信が発生します。これは送信効率が悪いし、SendGridへのリクエストの数が増えるため、リクエスト数制限に引っかかるリスクも上がってしまいます。なるべく1度のリクエストで複数の宛先へメール送信すると効率よくメール送信することが可能です。

Web APIでは Personalizations で to を指定することで、SMTP APIでは X-SMTPAPI Header の to を指定することで、複数の宛先へメールを送信することができます。

※ 1回のリクエストで送信できるメールはSMTP APIの場合は10,000宛先まで、Web APIの場合は1,000宛先までのようです。しかし、SMTP APIを利用する場合でも宛先は1,000件までとする方がパフォーマンス的に最も良いということで推奨されています。
※ ただしSMTP APIの場合、この複数の宛先へ送信する機能をsubstitutionやsection tagsと同時に使うと1度に送信できる宛先が1,000件よりも大幅に減ることがあります。私の関わっているプロジェクトでは100件の送信でも不可能になってしまい最終的に同時送信数を10まで下げました。

(

SMTP APIの例

header に X-SMTPAPI というキーを追加し、以下のJSONを値に渡すことで taro@example.comjiro@example.com 宛にメールを送信できます。

{
  "to": ["taro@example.com", "jiro@example.com"],
}

※ ちなみにX-SMTPAPI Headerの to を指定した場合、通常のToヘッダで指定した宛先には送信されなくなるので注意が必要です。全ての宛先をX-SMTPAPIの to へ記載する必要があります。

複数の宛先へメール送信する際、宛先ごとにタイトルや本文を変更する

複数の宛先へメールを送信する際、「同じような内容を送りたいのだけど、一部の箇所は宛先ごとに異なる内容を送信したい」というユースケースには頻繁に遭遇すると思います。それぞれのプログラミング言語で使用しているテンプレートエンジンでそれらのユースケースに対応することも多いと思いますが、上述したSendGridの機能(1度のリクエストで複数の宛先へメールを送信する)を利用した場合、言語のテンプレートエンジンではなくSendGridで宛先ごとに一部の内容を置換する必要があります。

SMTP APIではX-SMTPAPI Header の Substitution Tags、Web APIでは Personalizations の substitutionsをそれぞれ利用することで、SendGrid上で宛先ごとに一部の内容を置換することができます。

https://sendgrid.kke.co.jp/docs/Tutorials/A_Transaction_Mail/send_with_substitution.html

SMTP APIの例

こちらも header に X-SMTPAPI というキーを追加し、以下のJSONを値に渡すことで実現できます。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
    </style>
  </head>
  <body>
    <p>
      Hello -nickname- 様
    </p>
  </body>
</html>
{
  "to": ["taro@example.com", "jiro@example.com"],
  "sub": {
    "-nickname-": ["TARO", "JIRO"]
  }
}

上記の例では taro@example.comjiro@example.com あてにメールが送信され、HTMLの Hello -nickname- 様 という箇所が、それぞれ Hello TARO 様 Hello JIRO 様 という形に置換されます。

並列でリクエストをする

上述の項目でなるべく1回のリクエストで複数の宛先へメール送信する方が効率が良いと記述しました。SendGridへリクエストを送る処理は時間がかかるためというのが理由でしたが、非常に多くの宛先へメールを送信しなければならない際は、上述の方法でリクエストを行ったとしても複数回のリクエストを送らなければならないケースがあると思います。このようなケースでは並列でリクエストを実行することで処理時間の短縮が見込めます。

例えば 50,000件の宛先へメール送信したい場合、1回のリクエストで 1,000件ずつ送るとして、全ての宛先へメール送信するまで合計50回のリクエストを送る必要があります。1回リクエストを行うたびにSendGridと通信を行うので、リクエスト毎に待ち時間が発生します。

直列にリクエストを実行した場合、以下のように実行されます。

1回目のリクエスト => (待ち時間) => 2回目のリクエスト => (待ち時間) => (中略)... 50回目のリクエスト => (待ち時間) => (送信完了)

直列にリクエストを実行した場合、処理全体にかかる時間は

(1回リクエスト処理にかかる時間 x 50) + (1回のリクエストごとにかかる待ち時間 x 50)

となります。

並列でリクエストを実行した場合、以下のように実行されます。

※並列処理の実装次第ですが、以下の1回目のリクエスト〜50回目のリクエストは(大体)同時に実行されるとします

- 1回目のリクエスト => (待ち時間)
- 2回目のリクエスト => (待ち時間)
- (中略)...
- 50回目のリクエスト => (待ち時間)

並列でリクエストを実行した場合、処理全体にかかる時間は

(1回目のリクエスト処理にかかる時間 + 1回目のリクエストの待ち時間)
〜
(50回目のリクエスト処理にかかる時間 + 50回目のリクエストの待ち時間)
の中で一番大きい時間

となります。このように並列でリクエストすることで、全てのメール送信するまでにかかる時間の短縮が期待できます。

※ ちなみに一般的なWebアプリケーションの場合、リクエストの処理にかかる時間よりも待ち時間の方が大きくなることがほとんどだと思います。

特定の時間に一斉にメールを送信する

特定の時間に一斉にメールを送信したい場合、SendGridのスケジューリング機能を使うことができます。 SMTP API・ Web API共に send_at という項目を指定してあげることでその時間にSendGridでメール送信を実施してくれます(send_atの値はunix timestamp形式である必要があります)。より厳密にメール配信時間を指定したい場合には利用できそうです。

また大量のメールを送信する場合、send_atを指定することでSendGrid側のメール送信処理の効率アップも期待できるようです。
(※SendGridへリクエストを送信完了したら宛先へのメール送信が完了する訳ではなく、リクエスト後に SendGridのサーバーから各宛先へメール送信する処理 が実施されます。send_atを指定することで、この処理が効率よく実施できるようになるようです。)

SMTP APIの例

header に X-SMTPAPI というキーを追加し、以下のJSONを値に渡すことで taro@example.comjiro@example.com 宛のメールを 2022/12/24 19:00 JST ( unix timestampにすると 1671876000) に送信できます。

{
  "to": ["taro@example.com", "jiro@example.com"],
  "send_at": 1671876000
}

スロットリング - 多くのユーザーへメール送信する際の注意点

1度にたくさんのメールを送信するにあたって、スロットリングという現象に注意しなければなりません。 我々がSendGridへメール送信のリクエストを送った後、SendGridのサーバーから各宛先へメールを送信しますが、スロットリングが発生すると受信側のサーバーでメールの受信を遅延させたり、ブロックしてしまいます。つまりSendGrid上でメールを送信しても実際のユーザーが受信するまで遅延したり、届かない現象が発生します。

スロットリングが発生する大きな要因になるのは、急激に多くのメールを送信しようとすることのようです。 例えば今までは1日最大でも10通のメールしか送っていなかったのに、ある時急に1日10,000件のメールを送信しようとするなど、過去のメール送信数と比較して急激にメール送信数が増えた時はスロットリングが発生するリスクが高いようです。

システムのメール数はなるべく把握しておき、メール送信数が急増しそうな時は事前に予防策を実施するようにしましょう。(「IPウォームアップ」や「宛先リストのクリーニング」がスロットリングの予防になるようです。以下のURLの記事に記載してあります。)

実際にスロットリングが発生しているかどうかは、SendGridの Activity Feed や Statsを確認することで把握することが可能です。

参考になるURL

https://sendgrid.kke.co.jp/blog/?p=10380
https://sendgrid.kke.co.jp/blog/?p=489
メールが遅延する場合はどうすればよいですか?

まとめ

これらの機能を使いこなすことでSendGridで大量のメールを効率よく配信することができます。特に1回のリクエストで複数の宛先へメールを送信する対応については、やっている場合とやらない場合で大きくメール送信時間に影響がありそうです。

関わっているプロジェクトで使えそうな知見があればぜひ取り入れてもらえたら嬉しいです!

MIXI DEVELOPERS NOTE
MIXI DEVELOPERS NOTE

Discussion