Webhook APIを使って添付ファイル付きメールを処理する(AWS Lambda + Ruby)

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

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

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

<!—more—>

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

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

Webhook設定ダイアログ

AWSの準備

まずAWSにてLambdaの関数を作成します。

今回はウィザードの中でAPI GatewayのHTTPエンドポイントを設定しています。

ローカルで開発する際にはAWS CLIAWS SAM CLIをインストールします。また、Dockerのインストールも必要です。

AWSのIAMでLambdaの操作ができるユーザーを作成したら、aws configure コマンドでAPIキーを設定します。

なお、筆者環境ではローカルで multipart/form-data を受けようとすると UnicodeDecodeError while processing HTTP request: 'utf-8' codec can't decode byte といったエラーになってしまいました。これはローカルのPython側のエラーであり、Lambda上では発生しません。

関数を作成する

関数のベースは次のコマンドで作成できます。

$ sam init --runtime ruby3.2  

そしてローカルで関数を実行します。

$ sam local start-api

これで http://localhost:3000 で関数を呼び出せます。

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

メールを受信すると、以下のような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": "...."
}

Rubyのコード

以下のコードは app.rb の中身です。

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

まずはマルチパートフォームデータをパースする処理を追加します。これはparsing - Rails: get multipart/form-data POST request parameters with same name - Stack Overflowを参考にしています。以下の内容を util.rb として作成しています。

require 'multipart_parser/reader'
require 'base64'

module Util
  extend self

  # parse a multipart MIME message, returning a hash of any multipart errors
  def parse_multipart(event)
    boundary = MultipartParser::Reader::extract_boundary_value(event["headers"]["content-type"])
    reader = MultipartParser::Reader.new(boundary)
  
    parts={}
    reader.on_part do |part|
      pn = part.name.to_sym
      part.on_data do |partial_data|
        if parts[pn].nil?
          parts[pn] = partial_data
        else
          parts[pn] = [parts[pn]] unless parts[pn].kind_of?(Array)
          parts[pn] << partial_data
        end
      end
    end

    reader.write Base64.decode64(event["body"])
    reader.ended? or raise Exception, 'truncated multipart message'
    parts
  end
end

lambda_handlerの処理

上記 util.rb を読み込み、送られてきたデータをパースします。結果はそのままシンボルで各データにアクセスできます。

添付ファイルは attachment1attachment2 でアクセスできるので、Amazon S3などに保存もできるでしょう。添付ファイルがあるかどうかは attachments キーの値が1以上かどうかで判定できます。

require 'json'
require './util'

def lambda_handler(event:, context:)
  begin
    body = Util.parse_multipart(event)
    puts "server_composition: #{body[:server_composition]}"
    if body[:attachments] > 0
      attachment = body[:attachment1]
      puts attachment
    end
  rescue => e
        return {
            statusCode: 400,
            body: {
                message: e.message,
            }.to_json
        }
  end
  {
    statusCode: 200,
    body: {
      message: "ok",
    }.to_json
  }
end

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

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

Webhookログ

まとめ

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

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

受信サーバ | Customers Mail Cloud