Customers Mail CloudのWebhookは2種類あります。
- メール受信時
- メール送信時
メール受信時のWebhookはその名の通り、メールを受け取った際に任意のURLをコールするものです。この記事では添付ファイル付きメールを受け取った際のWebhook処理について解説します。
フォーマットはマルチパートフォームデータ
Webhookの形式として、JSONとマルチパートフォームデータ(multipart/form-data)が選択できます。この二つの違いは、添付ファイルがあるかどうかです。JSONの場合、添付ファイルは送られてきません。今回のようにメールに添付ファイルがついてくる場合は、後者を選択してください。
送信されてくるデータについて
メールを受信すると、以下のような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": "...." }
AWS Lambdaの準備
AWS Lambdaで新しい関数を作成します。その際、条件として以下を指定します。
- ランタイム
Python 3.13 - 関数 URL を有効化
チェックを入れる - 認証タイプ
NONE
関数の作成が完了したら、関数コードを編集します。ベースになるコードは以下の通りです。
import json def lambda_handler(event, context): return { 'statusCode': 200, 'body': json.dumps('ok') }
Pythonのコード
処理は lambda_function.py
に記述します。
import json def lambda_handler(event, context): return { 'statusCode': 200, 'body': json.dumps('ok') }
マルチパートフォームデータを取得する
添付ファイルを処理する際には requests-toolbelt
というライブラリを利用します。
pip install requests-toolbelt -t .
そしてライブラリをインポートします。
import base64 from requests_toolbelt.multipart import decoder from email.header import decode_header, make_header
最初にマルチパートのデータをデコード処理します。
# Content-Type ヘッダーの取得 content_type = event['headers'].get('Content-Type') or event['headers'].get('content-type') # Content-Type が multipart/form-data であることを確認 if content_type and content_type.startswith('multipart/form-data'): # リクエストボディの取得とデコード body = event['body'] if event.get('isBase64Encoded', False): body = base64.b64decode(body) else: body = body.encode('utf-8') # マルチパートデータの解析 multipart_data = decoder.MultipartDecoder(body, content_type)
そして、 multipart_data
に対してフィールドとファイルの取得を行います。
fields = {} files = [] for part in multipart_data.parts: content_disposition = part.headers.get(b'Content-Disposition', b'').decode('utf-8') if 'filename' in content_disposition: # ファイルデータの処理 file_data = part.content # ファイル名を取得 filename = content_disposition.split('filename=')[1].strip('"') decoded_filename = decode_rfc2047(filename) files.append({ 'filename': decoded_filename, 'content': file_data }) print(f"Received file: {decoded_filename} ({len(file_data)} bytes)") else: # フォームフィールドの処理 field_name = content_disposition.split('name=')[1].strip('"') field_value = part.text print(f"Received field: {field_name} = {field_value}") fields[field_name] = field_value
ファイル名はデフォルトでエンコードされています。そこで、 decode_rfc2047
という関数を作って、デコードします。
def decode_rfc2047(encoded_string): # エンコードされた文字列をデコード decoded_tuple = decode_header(encoded_string) # デコードされた部分を結合して完全な文字列を作成 decoded_string = str(make_header(decoded_tuple)) return decoded_string
これで、処理結果を fields
や files
で取得できます。
print(fields['server_composition']) # pro print(files[0]['filename']) # 稟議書.docx
ファイルを保存する場合には、 content
にデータが入っています。
console.log(files[0]['content']); // ファイルの内容
Webhookの結果は管理画面で確認
Webhookでデータが送信されたログは管理画面で確認できます。送信時のAPIキー設定など、HTTPヘッダーを編集するといった機能も用意されていますので、運用に応じて細かなカスタマイズが可能です。
まとめ
メールと連携したシステムはよくあります。通常、メールサーバを立てて、その中で処理することが多いのですが、メールサーバが落ちてしまうとシステムが稼働しなくなったり、メール文面の解析が煩雑でした。Customers Mail Cloudを使えばそうした手間なくJSONで処理できて便利です。
添付ファイルまで処理対象にしたい時には、この方法を利用してください。