Customers Mail CloudのWebhookは2種類あります。
- メール受信時
- メール送信時
メール送信時は、送信したメールに対してステータスが変わったタイミングで通知が送られるものです。
その際、 application/json
を指定しない設定ができます。この時のデータがどうなっているのか紹介します。
<!—more—>
Azure Functionsの準備
まずAzure Functionsにて関数を作成します。Azure FunctionsはVisual Studio Codeから作成できます。ウィザードに沿って進めていくだけで作成できるので簡単です。
今回は .NET6 を使い、HTTPトリガーを選択しています。また、ネームスペースは Smtps.Function
で、関数名は CMCWebhook
としています。
関数を作成する
関数のベースは以下のようになります。
using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System.Collections.Generic; using System.Web; namespace Smtps.Function { public static class CMCWebhook { [FunctionName("CMCWebhook")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log) { return new ContentResult(){Content = "{\"result\": \"ok\"}", ContentType = "application/json"}; } } }
受け取るWebhookの設定
管理画面にて、受け取るWebhookを設定できます。設定は以下が用意されています。
- Bounces
- bounced(エラーメールを受け取る)
- Deliveries
- queued(キューに入ったタイミング)
- succeeded(送信完了)
- failed(送信失敗)
- deferred(送信延期)
この中で application/json
を指定できます。指定しなかった場合、データは application/x-www-form-urlencoded
にて送信されます。本記事ではこの場合を想定しています。
送信されてくるデータについて
メール送信した直後
メール送信を行うと、そのデータがキューに入ります。そして、以下のようなWebhookが送られてきます(データは一部マスキングしています)。データは分かりやすいようにJSONにしていますが、実際には異なりますので注意してください。
{ "event_type": "deliveries", "server_composition": "pro", "event": '{"deliveries":[{"reason":"","sourceIp":"100.100.100.1","returnPath":"info@return.pro.smtps.jp","created":"2023-01-25 14:03:06","subject":"メールマガジンのテスト","apiData":"","messageId":"<031a32d4-06cd-b1ae-9526-011c0b9f1296@example.com>","from":"info@example.com","to":"user@example.jp","senderIp":"","status":"queued"}]}' }
メール送信完了時
Customers Mail Cloudからメール送信処理が行われると、ステータスが succeeded
になったWebhookが送られてきます。
{ "event_type": "deliveries", "server_composition": "pro", "event": '{"deliveries":[{"reason":"","sourceIp":"","returnPath":"info@return.pro.smtps.jp","created":"2023-01-25 14:03:09","subject":"メールマガジンのテスト","apiData":"","messageId":"<031a32d4-06cd-b1ae-9526-011c0b9f1296@example.com>","from":"info@example.com","to":"user@example.jp","senderIp":"100.100.100.3","status":"succeeded"}]}' }
メール送信失敗時(メールアドレス形式に問題がある場合)
メールアドレスの形式に問題があるなど、送信処理が失敗した場合には以下のようなWebhookが送られてきます。
{ "event_type": "bounces", "server_composition": "pro", "event": '{"bounces":[{"reason":"host unknown","returnPath":"info@return.pro.smtps.jp","created":"2023-01-25 14:05:15","subject":"メールマガジンのテスト","apiData":"","messageId":"<8f902ee7-ae65-8711-48a8-2f708cb14205@example.com>","from":"info@example.com","to":"user@example","status":"1"}]}' }
メール送信失敗時(送信先サーバーからエラーが返ってくる場合)
ユーザーが存在しない、メールボックスがいっぱいなど送信先サーバーからエラーが返ってきた場合には、以下のようなJSONが返ってきます。
{ "event_type": "deliveries", "server_composition": "pro", "event": '{"deliveries":[{"reason":"550 5.1.1 The email account that you tried to reach does not exist. Please try 5.1.1 double-checking the recipient's email address for typos or 5.1.1 unnecessary spaces. Learn more at 5.1.1 <https://support.google.com/mail/?p=NoSuchUser> b197-20020a621bce000000b0058b80756b07si311029pfb.3 - gsmtp (in reply to RCPT TO)","sourceIp":"","returnPath":"info@return.pro.smtps.jp","created":"2023-01-25 14:06:06","subject":"メールマガジンのテスト","apiData":"","messageId":"<9e7e564c-ac83-8cd8-2cb4-b9ff2a9f168d@example.com>","from":"info@example.com","to":"no-user@example.jp","senderIp":"100.100.100.3","status":"failed"}]}' }
エラーとしてのWebhookも送られてきます。上記のものと event_type
が異なるので注意してください。
{ "event_type": "bounces", "server_composition": "pro", "event": '{"bounces":[{"reason":"550 5.1.1 The email account that you tried to reach does not exist. Please try 5.1.1 double-checking the recipient's email address for typos or 5.1.1 unnecessary spaces. Learn more at 5.1.1 <https://support.google.com/mail/?p=NoSuchUser> b197-20020a621bce000000b0058b80756b07si311029pfb.3 - gsmtp (in reply to RCPT TO)","returnPath":"info@return.pro.smtps.jp","created":"2023-01-25 14:06:07","subject":"メールマガジンのテスト","apiData":"","messageId":"<9e7e564c-ac83-8cd8-2cb4-b9ff2a9f168d@example.com>","from":"info@example.com","to":"no-user@example.jp","status":"2"}]}' }
Webhookの処理
まず、JSONデータを受け取るクラスを作成します。JSONデータのキー名と同じ名前のプロパティを作成します。JSONデータのキー名はスネークケースですが、C#のプロパティ名はキャメルケースにしています。
public class EventType { public string reason { get; set; } public string returnPath { get; set; } public string created { get; set; } public string subject { get; set; } public string apiData { get; set; } public string messageId { get; set; } public string from { get; set; } public string to { get; set; } public string status { get; set; } } public class Event { public List<EventType> bounces { get; set; } public List<EventType> deliveries { get; set; } public List<EventType> blocks { get; set; } } public class Email { public string event_type { get; set; } public string server_composition { get; set; } public Event @event { get; set; } }
次に、受け取ったデータをパースして、取得する処理を行います。 req.Body
で返ってくるのはクエリーストリング形式になっているので、 HttpUtility.ParseQueryString
を使ってパースし、さらにDictオブジェクトに入れ直しています。
event
キー以下は文字列なので、JsonConvert.DeserializeObjectを使ってデシリアライズしています。
var sr = new StreamReader(req.Body); var nv = HttpUtility.ParseQueryString(await sr.ReadToEndAsync()); var dict = new Dictionary<string, object>(); foreach (var key in nv.AllKeys) { if (key == "event") { var eventJson = JsonConvert.DeserializeObject<Event>(nv[key]); dict.Add(key, eventJson); } else { dict.Add(key, nv[key]); } } var jsonString = JsonConvert.SerializeObject(dict); log.LogInformation(jsonString); var json = JsonConvert.DeserializeObject<Email>(jsonString);
全体のコードは以下のようになります。
using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System.Collections.Generic; using System.Web; public class EventType { public string reason { get; set; } public string returnPath { get; set; } public string created { get; set; } public string subject { get; set; } public string apiData { get; set; } public string messageId { get; set; } public string from { get; set; } public string to { get; set; } public string status { get; set; } } public class Event { public List<EventType> bounces { get; set; } public List<EventType> deliveries { get; set; } public List<EventType> blocks { get; set; } } public class Email { public string event_type { get; set; } public string server_composition { get; set; } public Event @event { get; set; } } namespace Smtps.Function { public static class CMCWebhook { [FunctionName("CMCWebhook")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log) { var sr = new StreamReader(req.Body); var nv = HttpUtility.ParseQueryString(await sr.ReadToEndAsync()); var dict = new Dictionary<string, object>(); foreach (var key in nv.AllKeys) { if (key == "event") { var eventJson = JsonConvert.DeserializeObject<Event>(nv[key]); dict.Add(key, eventJson); } else { dict.Add(key, nv[key]); } } var jsonString = JsonConvert.SerializeObject(dict); var json = JsonConvert.DeserializeObject<Email>(jsonString); log.LogInformation(json.server_composition); log.LogInformation(json.@event.bounces[0].reason); return new ContentResult(){Content = "{\"result\": \"ok\"}", ContentType = "application/json"}; } } }
まとめ
Webhookを使うことで、メール送信ステータスの変化に応じて通知を受け取れるようになります。メールと連携したシステムを開発する際に役立つでしょう。
C#の場合は application/json
を指定した方が全体として、受け取りやすい印象です。ぜひお試しください。なお、このWebhookはSMTP経由の場合、利用できます。