Customers Mail Cloudではプログラム側からデータを取得したり、メールを送信するWeb APIの他に、Customers Mail Cloudでメールを受信した時にイベントを伝えてくれるWebhook APIが用意されています。
Webhook APIを使うことで、自前でメールサーバを立てずにメール受信のタイミングでシステムを起動させられるようになります。メールサーバを安定して動作させ続けるのはメンテナンスコストが大きいですが、Customers Mail Cloudを使うことで簡単にメールと連携したシステムが作れるようになるでしょう。
今回はGoogle Cloud Platform(GCP)で、Go言語を使ってメールを処理する流れを紹介します。
フォーマットはJSONとマルチパートフォームデータ
Webhookの形式として、JSONとマルチパートフォームデータ(multipart/form-data)が選択できます。この二つの違いは、添付ファイルがあるかどうかです。JSONの場合、添付ファイルは送られてきません。メールに添付ファイルがついてくる可能性がある場合は、後者を選択してください。
今回はJSONフォーマットにおけるWebhook処理について紹介します。
GCPの準備
まずGCPにてプロジェクトを作成します。このプロジェクトは課金設定されている必要があります。
以下5つのAPIを有効にします。
- Cloud Functions
- Cloud Build
- Artifact Registry
- Cloud Run
- Cloud Logging API
gcloud CLIをインストールします。各OS別にダウンロードするバイナリが異なるので注意してください。
ファイルを伸張したら、installコマンドを実行します。
$ /path/to/google-cloud-sdk/install.sh
インストールすると gcloud
コマンドが使えるようになります。続けてコンポーネントをアップデートします。
$ gcloud components update
関数を作成する
まずCloud Functionsにアップロードする関数を作ります。適当なディレクトリを作成し、その中に移動します。
$ mkdir cmc $ cd cmc
そしてGoファイルを作成します。今回は webhook.go としています。
package helloworld import ( "encoding/json" "fmt" "html" "net/http" "github.com/GoogleCloudPlatform/functions-framework-go/functions" ) func init() { functions.HTTP("HelloHTTP", HelloHTTP) } // HelloHTTP is an HTTP Cloud Function with a request parameter. func HelloHTTP(w http.ResponseWriter, r *http.Request) { // この中にコードを書きます // 最後にdoneと出力しておく fmt.Fprint(w, "done") }
依存関係を解決するため、 go.mod
を作成します。
$ go mod init example.com/hello $ go mod tidy
関数をローカルでテスト実行する
開発中はローカルで実行してテストするので、そのための準備をします。
$ mkdir cmd $ cd cmd
cmdディレクトリの中に main.go
を作成します。
package main import ( "log" "os" // Blank-import the function package so the init() runs _ "example.com/hello" "github.com/GoogleCloudPlatform/functions-framework-go/funcframework" ) func main() { // Use PORT environment variable, or default to 8080. port := "8080" if envPort := os.Getenv("PORT"); envPort != "" { port = envPort } if err := funcframework.Start(port); err != nil { log.Fatalf("funcframework.Start: %v\n", err) } }
先ほどと同じく cmd
の中でも依存関係を解決します。
$ go mod tidy
後は main.go を実行してHTTPサーバーを立ち上げます。
$ export FUNCTION_TARGET=HelloHTTP $ go run /path/to/cmd/main.go
これで http://localhost:8080
で関数を呼び出せます。
Webhookで受け取るデータについて
Webhookを使ってPOSTされるJSONデータは、次のようになっています。
{ "filter": "info@smtps.jp", "headers": [ {name: 'Return-Path', value: '<user@example.com>'}, : {name: 'Date', value: 'Thu, 27 Apr 2023 15:56:26 +0900'} ], "subject": "Webhookのテスト", "envelope-to": "user@smtps.jp", "server_composition": "sandbox", "html": "<div dir=\\\\\\\\\\\\\\\\"ltr\\\\\\\\\\\\\\\\">Webhookのテスト用メールです。<div>...</div></div>", "text": "Webhookのテスト用メールです。\\\\\\\\\\\\\\\\r\\\\\\\\\\\\\\\\n\\\\\\\\\\\\\\\\r\\\\\\\\\\\\\\\\n--\\\\\\\\\\\\\\\\r\\\\\\\\\\\\\\\\n...", "envelope-from": "info@smtps.jp" }
Goのコード
以下のコードは webhook.go
の中身です。
構造体の作成
上記JSONデータに基づいて構造体を用意します。
type Mail struct { Filter string `json:"filter"` Headers []Header `json:"headers"` Subject string `json:"subject"` EnvelopeTo string `json:"envelope-to"` ServerComposition string `json:"server_composition"` Html string `json:"html"` EnvelopeFrom string `json:"envelope-from"` Text string `json:"text"` } type Header struct { Name string `json:"name"` Value string `json:"value"` }
POST時の処理について
処理は HelloHTTP
関数の中に書きます。処理結果は done
という文字列を返すだけとしています。 json.NewDecoder
を使ってBodyを Mail
構造体に適用しています。
func HelloHTTP(w http.ResponseWriter, r *http.Request) { var mail Mail if err := json.NewDecoder(r.Body).Decode(&mail); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } fmt.Println(mail.Subject) fmt.Println(mail.EnvelopeFrom) fmt.Fprint(w, "done") }
Webhookの結果は管理画面で確認
Webhookでデータが送信されたログは管理画面で確認できます。送信時のAPIキー設定など、HTTPヘッダーを編集するといった機能も用意されていますので、運用に応じて細かなカスタマイズが可能です。
まとめ
メールと連携したシステムはよくあります。通常、メールサーバを立てて、その中で処理することが多いのですが、メールサーバが落ちてしまうとシステムが稼働しなくなったり、メール文面の解析が煩雑でした。Customers Mail Cloudを使えばそうした手間なくJSONで処理できて便利です。