Webhook APIを使って添付ファイル付きメールを処理する(Google Cloud Functions + Go言語)

Customers Mail CloudのWebhookは2種類あります。

  1. メール受信時
  2. メール送信時

メール受信時のWebhookはその名の通り、メールを受け取った際に任意のURLをコールするものです。この記事では添付ファイル付きメールを受け取った際のWebhook処理について解説します。

フォーマットはマルチパートフォームデータ

Webhookの形式として、JSONとマルチパートフォームデータ(multipart/form-data)が選択できます。この二つの違いは、添付ファイルがあるかどうかです。JSONの場合、添付ファイルは送られてきません。今回のようにメールに添付ファイルがついてくる場合は、後者を選択してください。

Webhook設定ダイアログ

送信されてくるデータについて

メールを受信すると、以下のようなWebhookが送られてきます(データは一部マスキングしています)。JSONにしていますが、実際にはmultipart/form-dataです。

{
    "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",
    "attachments": 1,
    "attachment1": "...."
}

Google Cloud Functionsの準備

今回はローカルで開発する流れを紹介します。まず、適当なフォルダを作成します。今回はcmcとします。

mkdir cmc
cd cmc

次に hello_http.go というファイルを作成し、以下のように記述します。

package helloworld

import (
        "encoding/json"
        "fmt"
        "html"
        "net/http"

        "github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

func init() {
        functions.HTTP("HelloHTTP", HelloHTTP)
}

func HelloHTTP(w http.ResponseWriter, r *http.Request) {
    // ここに処理を記述
}

次に依存関係を追跡します。

go mod init example.com/hello
go mod tidy

ローカルで実行できるようにする準備

cmc フォルダ内に cmd フォルダを作成します。

mkdir 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)
  }
}

残りの依存関係を解決します。

go mod tidy

後は以下のコマンドで http://localhost:8080 でサーバーが立ち上がります。

export FUNCTION_TARGET=HelloHTTP
go run cmd/main.go

Go言語のコード

処理は hello_http.goHelloHTTP 関数内に記述します。

func HelloHTTP(w http.ResponseWriter, r *http.Request) {
    // ここに処理を記述
}

マルチパートフォームデータを取得する

multipart/form-data で送られてくるデータは r.ParseMultipartForm にてパースします。

len := r.ContentLength
r.ParseMultipartForm(len)

この後はキーを指定して、その値を取得できます。

fmt.Fprintln(w, r.MultipartForm.Value["envelope-from"][0]) // info@example.com

添付ファイルの処理

添付ファイルの有無は r.MultipartForm.Value["attachments"][0] が0以上かどうかで判別できます。

添付ファイルは r.MultipartForm.File にてアクセスできます。1つ目の添付ファイルはキーが attachment1 、2つ目は attachment2 になります。

データが取り出せれば、後はファイルとして書き込んだり、解析を行えます。

fmt.Fprintln(w, r.MultipartForm.Value["attachments"][0])
fileHeader := r.MultipartForm.File["attachment1"][0]
file, err := fileHeader.Open()
if err == nil {
    data, err := io.ReadAll(file)
    if err == nil {
        fmt.Fprintln(w, string(data))
    }
}

Webhookの結果は管理画面で確認

Webhookでデータが送信されたログは管理画面で確認できます。送信時のAPIキー設定など、HTTPヘッダーを編集するといった機能も用意されていますので、運用に応じて細かなカスタマイズが可能です。

Webhookログ

まとめ

メールと連携したシステムはよくあります。通常、メールサーバを立てて、その中で処理することが多いのですが、メールサーバが落ちてしまうとシステムが稼働しなくなったり、メール文面の解析が煩雑でした。Customers Mail Cloudを使えばそうした手間なくJSONで処理できて便利です。

添付ファイルまで処理対象にしたい時には、この方法を利用してください。

受信サーバ | Customers Mail Cloud