ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

SendGrid Bounces API でバウンスリストからメールアドレスを削除する

Azure にデプロイした Web アプリケーションから、SendGrid を使ってメール送信しているのですが、docomo などのキャリアメールが割とバウンスしやすいです。バウンスした原因である受信拒否を解除しても、バウンスリストからメールアドレスを削除しないと、送信したメールが届きません。
sendgridjp.zendesk.com
SendGrid ポータルで、バウンスリストからメールアドレスを削除する運用が面倒になってきたこともあり、C# のプログラムから API を使って削除する方法を調べたのでまとめておきます。

SendGrid Bounces API

SendGrid には、バウンスリストを管理できる Web API が2つあります。

従来から Web API はありましたが、Web API v3 にも Bounces API が追加されました。提供されている機能が若干異なるのですが、バウンスリストから複数メールアドレスを一括で削除できる Web API v3 を使います。また、API キーによる認証がサポートされているので、セキュリティ的にもこちらのほうがお勧めです。

C# で Bounces API を呼び出す

Web API v3 の共通仕様Delete bounces API の仕様を参考にしながら、シンプルに HttpClient で、Bounces API を呼び出しています。

internal static class SendGridApiHelper
{
    private const string APIHOST = "https://api.sendgrid.com/v3/";
    private const string BOUNCESAPI = APIHOST + "suppression/bounces";
    private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    public static async Task DeleteBouncesAsync(List<string> emails)
    {
        var targets = emails.Select((value, index) => new { value, index }).ToLookup(x => x.index / 500, x => x.value);
        foreach (var target in targets)
        {
            await DeleteBouncesAsync(target);
        }
    }

    private static async Task DeleteBouncesAsync(IGrouping<int, string> emails)
    {
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "<SendGrid API Key>");
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            var request = new HttpRequestMessage(HttpMethod.Delete, BOUNCESAPI);
            request.Content = new StringContent(JsonConvert.SerializeObject(new { emails = emails.ToList() }));
            request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            var response = await client.SendAsync(request);

            IEnumerable<string> values;
            if (response.Headers.TryGetValues("X-RateLimit-Limit", out values))
            {
                var limit = values.First();
                Trace.TraceInformation("Limit : " + limit);
            }
            if (response.Headers.TryGetValues("X-RateLimit-Remaining", out values))
            {
                var remaining = values.First();
                Trace.TraceInformation("Remaining : " + remaining);
            }
            if (response.Headers.TryGetValues("X-RateLimit-Reset", out values))
            {
                var reset = UNIX_EPOCH.AddSeconds(Convert.ToInt64(values.First()));
                Trace.TraceInformation("Reset : " + reset.ToLocalTime().ToString());
            }

            if (response.IsSuccessStatusCode == false)
            {
                 var responseContent = await response.Content.ReadAsStringAsync();
                if (String.IsNullOrEmpty(responseContent) == false)
                {
                    Trace.TraceError("errors : " + responseContent);
                }
            }
            response.EnsureSuccessStatusCode();
        }
    }
}

ポイントは、3つあります。

  1. HttpClient の DeleteAsync() メソッドに Request Body を渡すことができないので、SendAsync() メソッドを使っています。DELETE の HttpRequestMessage を作って、Content にメールアドレスのリストを JSON でセットしています。Authorization ヘッダには、SendGrid ポータルで作成した API Key を渡します。
  2. メールアドレスを一括で削除できるのは 500 件までとなっているので、分割して API を呼び出しています。Linq の Enumerable.ToLookup() メソッドを使うと一発で分割することできて、いい感じでした。そもそも、こんなに多くのメールアドレスを一括で削除しないという話もありますが、他でも使えそうなロジックだと思います。
  3. Web API v3 には一定時間内に呼び出せる回数に制限があるため、レスポンスヘッダから情報を取得しています。回数上限に達した場合は、HTTP ステータスコードの 429(too many requests)が返されます。

まとめ

Bounces API を使えば、運用側の管理コストを減らしたり、ユーザーにバウンスを解除する機能を提供したりできます。しかし、必要以上にバウンスリストからメールアドレスを削除してしまうと、レピュテーションの低下に繋がりますので、利便性とリスクのトレードオフを十分に考慮したうえで使うようにしましょう。