ツナ缶雑記

ぐうたらSEのブログです。主にマイクロソフト系技術を中心に扱います。

SendGridで送信遅延が発生したときの挙動

SendGridとは

メール送信を行うためのクラウドベースのサービスです。Azureサブスクリプション (Visual Studioサブスクリプションを含みます) を持っている場合、Azure PortalからSendGridのアカウントを作成することができます。Azure Portalには2019/7/7現在、以下のような説明が記載されています。

SendGrid is the world's largest cloud-based service for delivering email that matters. SendGrid's proven platform successfully delivers over 18B transactional and marketing related emails each month for Internet and mobile-based customers like Airbnb, Pandora, Hubspot, Spotify, Uber and FourSquare as well as more traditional enterprises like Walmart, Intuit and Costco. Azure customers receive up to 25,000 emails per month for free with paid packages starting at only $9.95 per month. For customers requiring ability to send larger email volumes, SendGrid also offers Silver, Gold, Platinum and Premier packages, which include a dedicated IP as well as additional IP and user management features.

月間25,000通までは無料でメール送信ができてしまうというお手軽さです。最近Azureでメール送信となるとほぼこれを使っています。ちなみにAzureサブスクリプションと紐づけてSendGridを使用する場合、いろいろ制限があります。詳細は以下のリンク先を参照してください。 blogs.msdn.microsoft.com

また課金体系についてはこちらが参考になります。 azuremarketplace.microsoft.com

送信遅延が発生

さて、本題です。先日私が運用しているSendGridアカウントにて、送信遅延が発生しました。送信遅延が発生すると、SendGridのポータルや、Web APIを経由して、送信遅延が発生していたことを知ることができます。ちなみにSendGridのWeb APIを使用する場合、Global Statsを使用することで、送信遅延の有無を知ることができます。

送信履歴の集計結果をC#プログラムで取得する

サクッと確認するなら素のWeb APIをたたいたり、SendGridのポータルを確認してもいいのですが、C#から取得したいならSendGridのNuGetパッケージを使うのもアリです。

今回は.NET CoreのコンソールアプリケーションからSendGridのWeb APIを叩いてみます。まずは.NET CoreのコンソールアプリケーションプロジェクトをVisual Studioで生成します。作成したプロジェクトに対して、[Sendgrid] という名前のNuGetパッケージを追加します。

f:id:masatsuna:20190707235908p:plain
Sendgridパッケージの追加

そして、以下のようにコードを書きます。

using System;
using SendGrid;

namespace SendGridClientTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Run().Wait();
        }

        private static async System.Threading.Tasks.Task Run()
        {
            string apiKey = "<SendGridのポータルで生成したAPIキー>";
            var client = new SendGridClient(apiKey);
            string queryParams = @"{
                'aggregated_by': 'day', 
                'end_date': '2019-06-29', 
                'limit': 1, 
                'offset': 1, 
                'start_date': '2019-06-29'
            }";
            var response = await client.RequestAsync(method: SendGridClient.Method.GET, urlPath: "stats", queryParams: queryParams);
            string responseStr = await response.Body.ReadAsStringAsync();
            Console.WriteLine(response.StatusCode);
            Console.WriteLine(responseStr);
        }
    }
}

これを実行すると、コンソール画面に指定した日のメール送信結果を集計した内容が表示されます。

f:id:masatsuna:20190708000745p:plain
実行結果

ちょっと結果が読みにくいので、JSON部分をフォーマットすると以下のようになります。

[
  {
    "date": "2019-06-29",
    "stats": [
      {
        "metrics": {
          "blocks": 0,
          "bounce_drops": 0,
          "bounces": 0,
          "clicks": 0,
          "deferred": 15,
          "delivered": 10,
          "invalid_emails": 0,
          "opens": 1,
          "processed": 10,
          "requests": 10,
          "spam_report_drops": 0,
          "spam_reports": 0,
          "unique_clicks": 0,
          "unique_opens": 1,
          "unsubscribe_drops": 0,
          "unsubscribes": 0
        }
      }
    ]
  }
]

送信遅延があったことを表す "deferred" というプロパティに15が記録されています。これは送信遅延が15回あったことの証拠になります。

送信遅延時の動作

さて、続いてSendGridのポータル画面から、送信遅延時にSendGridがどのような動作をしているのか確認してみます。

まず前提として、SendGridの仕様に関するページ*1を参照すると、以下のような記載があります。

Deferredが発生した場合、SendGridは時間を置いて再送信を繰り返します(72時間)。

では実際の動作はどうなっているのか、SendGridのポータル画面から確認します。SendGridの詳細ログは、[Activity] メニューから確認することができます。送信履歴の中から、[TYPE] 列が "deferred" となっているレコードを探します。

f:id:masatsuna:20190714233536p:plain
送信遅延時の挙動

今回のケースでは、5名に対するメール送信を同時に実行しています。それがそれぞれ3回ずつ遅延し、結果として15回の送信遅延が発生していた様子がわかりました。SendGridのログを見る限り、メール送信のリクエストを受け付けてから、 "deferred" が記録されるまでの間隔はおおよそ10分間のようです。この間実際に何回の再送処理が行われているのか、ログから正確なことを判断することはできませんが、少なくともこのログが記録されている瞬間には、再送を行っていたと考えられます。

ただ、根本的な問題がどこにあったのか、SendGridのポータルから確認することはできません。わかるのは、あくまで送信遅延があったという事実のみで、その原因が何なのかを知ることはできません。

なお、SendGridのポータルのダッシュボードに表示されているメール送信の成功率は、送信遅延による送信失敗はカウントされません。送信遅延が発生したとしても、最終的にメールが届いていれば、送信成功としてカウントされます。

メール送信時の挙動

ではメールを送信したアプリケーション側ではどのような挙動を示すのか、その答えはSendGridのメール送信用Web APIに答えがあります。メール送信用Web APIの解説の最下部に、HTTPリクエストの応答メッセージ例が示されています。

Response Code: 202 Accepted

そう、HTTPステータスコードは202 Acceptedなんですね。202は、要求を受け付けたけものの、まだ処理をしていないことを示すステータスコードです。

RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content

結論、アプリケーション側にはエラーやタイムアウト、遅延している、といった情報は一切通知されず、正常に処理を受け付けたことだけが返されます。

まとめ

今回はSendGridの送信遅延が発生したときの内部動作について書いてみました。ちょっと厄介なのは、送信遅延が発生したとき、SendGridが72時間も再送を繰り返す仕様です。通常メールの不達が発生したら、アプリケーション側である程度制御したいケースというのはあると思うのですが、それが許されないのはちょっとだけ厄介ですね。