SendGrid の Event Webhook を使って統計情報を取得する

クロスワープの大鷲です。
ご無沙汰しております。

MODD ではメルマガ配信に SendGrid を使っています。
最近やっと、メルマガごとの到達率や開封率、URL のクリック率などを、MODD の管理画面上で表示できるようになりました。

f:id:cw_owashi:20170628143523p:plain

統計情報を取得したい

SendGrid のダッシュボード上では、こんな風に、メルマガ(SendGrid 用語ではマーケティング キャンペーンという)の各種統計情報を表示することができます。
f:id:cw_owashi:20170628145603p:plain
ところが、どうやらこれは内部で非公開 API を叩いているらしく、公開された方法では、キャンペーン ID を指定して、このような情報を取得するための API がありません。

Stats API というのもありますが、これは指定した期間内の複数のメルマガに関する統計を合算して取得してしまうので、キャンペーン ID ごとという目的は満たせません。
Category Stats が使えるかと思ったこともありますが、カテゴリー機能は文字通りカテゴリーとして使うべきであって、一意な ID として使うには適さないようです。
一意な ID が欲しければ Unique Arguments を使うようにと書かれていますが、Unique Arguments をキーにして Stats を取得する API は無く、また、そもそもマーケティング キャンペーンには Unique Arguments を付加できません。

というわけで、同じことをアプリ内でやろうとする場合、Event Webhook を使って溜めたデータを自分で集計するしかありません。

Event Webhook

Event Webhook は、SendGrid 上で何らかのイベント*1が発生した時に、あらかじめ設定しておいた HTTP エンドポイントにイベント情報を JSON で POST してくれる機能です。
AWS Lambda などを使って実装されている例も見受けられますが、MODD では現状、普通の ASP.NET アプリケーションとして実装しています。
大量のメールを配信すると、Webhook にもアクセスが殺到しますので、できる限り軽量に作っておく必要があります。
そのため、JSON のパースは行わず、リクエスト ボディを丸ごと文字列化してデータベースに保存するだけにしています。

パースは後で別のバッチ アプリケーションが行います。
JSON の配列を分解して、オブジェクト1つを1レコードとして、別のテーブルに入れ直しています。
メルマガ単位で集計するのが目的なので、メール ID(marketing_campaign_id と対応する業務側の ID)をキーとして持たせています。
また、トランザクション メールにも対応させるため、Unique Arguments のうち、一定の命名規約に従うものをメール ID として扱うようにもなっています。

その他、細かいポイント

トラッキング設定の有効化

Event Webhook を有効にするには、SendGrid の設定で Event Notification を有効にする必要がありますが、開封とクリックを捉えるには、さらに Tracking の設定も必要です。
これを忘れていると、Open / Click イベントは通知されません。

開封イベントと画像表示

Open イベントは HTML メールに、SendGrid のサーバーにある微小な画像へのリンクを埋め込み、その画像へのリクエストによって捕捉していますが、メーラーによっては、サーバーサイドの画像をデフォルトでは表示しないものもあります。
そのため、Open イベントは発生していなくても、ユーザーにはメールが読まれているということはあり得ます。

なお、MODD のメルマガ機能では、HTML メールも、テキストのみの入力も可能になっていますが、SendGrid のマーケティング キャンペーン機能では HTML メールしか配信できないため、テキスト メールの場合は、内部でごく簡単な HTML への変換処理をしています(トランザクション メールの場合は SendGrid が自動的に HTML 化します*2)。
開封率をより正確に捕捉したい場合は、ユーザーが画像を表示したくなるようなデザインのメールにするとよいのではないかと思います。

一時的な状態の扱い

基本的にイベントは、起こった事実として保存され、消えることはありません。
たとえば、Delivered イベントが起きた後で(不幸にも)ユーザーによって配信解除され、Unsubscribe イベントが発生したとしても、届いたという事実は消えないので、到達件数が減ることはありません。
しかし、視点によっては、例外もあり得ます。

ひとつは Deferred イベントです。
大量のメールを送信したためにスロットリングが発生している最中などは非常に重要な指標となりますが、統計値として後で見返すような数字ではないと思うので、MODD の集計上は、あるメールアドレスに対する最新のイベントが Deferred である場合のみ集計にカウントし、その後で Delivered や Bounce が来た場合には除いています*3

もうひとつは、Delayed Bounce という現象で、Delivered イベントが発生した後で Bounce イベントが起きるというものです。
これは結局、エンド ユーザーには届いていないわけですから、Delivered イベントとしてさほど意味があるとは思えません。
このケースも、同一宛先に対する、より新しい Bounce イベントがある場合は、到達件数から除くようにしています。

繰り返しになりますが、これらは MODD の管理画面に表示すべき情報として適切かどうかという視点ですので、別の視点(トラブルシューティングなど)では、隠すべきでないこともあります。

登録に失敗した宛先の扱い

メールアドレスによっては、SendGrid に登録できないものも存在します。
例えば、連続するドットを含むアドレス*4が挙げられます。
ドキュメントにはこのようなアドレスにも対応していると書かれていますが、実はこれはトランザクション メールの話で、マーケティング メールには当てはまりません。
このようなアドレスは、Drop イベントが発生するわけでもなく、Contacts に登録する段階でエラーになって弾かれてしまいます。
当然、Webhook の通知対象にもならないので、こうしたアドレスについては、内部的に登録エラーを表すイベント データ*5を生成して登録するようにしています。

*1:宛先に届いた、エラーが返された、開封された、リンクがクリックされた…など

*2:設定でオフにすることも可能です

*3:データベース上には保存されているので、見ようと思えば見ることは可能です

*4:RFC 的には不正な形式ですが、以前は一部の携帯キャリアでは使えていたため、現在でも存在します

*5:Event Webhook が通知してくるイベントには存在しない独自のタイプ