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

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": "...."
}

AWS Lambdaの準備

AWS Lambdaで新しい関数を作成します。その際、条件として以下を指定します。

  • ランタイム
    Node.js 22.x
  • 関数 URL を有効化
    チェックを入れる
  • 認証タイプ
    NONE

関数の作成が完了したら、関数コードを編集します。ベースになるコードは以下の通りです。

export const handler = async (event) => {
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

JavaScriptのコード

処理は index.mjs に記述します。

export const handler = async (event) => {
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

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

添付ファイルを処理する際には busboy というライブラリを利用します。

import Busboy from 'busboy';

そして、全体を Promise でラップします。

export const handler = async (event) => {
  const { body } = await new Promise((resolve, reject) => {
    const busboy = Busboy({
      headers: {
        'content-type': event.headers['Content-Type'] || event.headers['content-type'],
      },
    });

    const result = {
      files: [],
      fields: {},
    };

    busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
      const fileData = [];
      file.on('data', (data) => {
        fileData.push(data);
      }).on('end', () => {
        result.files.push({
          fieldname,
          filename: typeof filename === 'string' ? filename : decodeFilename(filename.filename),
          encoding,
          mimetype,
          content: Buffer.concat(fileData),
        });
      });
    });

    busboy.on('field', (fieldname, val) => {
      result.fields[fieldname] = val;
    });

    busboy.on('finish', () => {
      resolve({
        statusCode: 200,
        body: result,
      });
    });

    busboy.on('error', (error) => {
      reject({
        statusCode: 500,
        body: JSON.stringify({ error: error.message }),
      });
    });

    busboy.write(event.body, event.isBase64Encoded ? 'base64' : 'binary');
    busboy.end();
  });

decodeFilename は、ファイル名のデコードを行う関数です。日本語ファイル名の場合に利用できます。

const decodeFilename = (encodedFilename) => {
  // エンコード形式の解析
  const matches = encodedFilename.match(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/i);
  if (!matches || matches.length !== 4) {
    throw new Error("無効なエンコード形式です");
  }
  const charset = matches[1]; // 文字セット(例: UTF-8)
  const encodedText = matches[3]; // エンコードされたテキスト
  const buffer = Buffer.from(encodedText, 'base64');
  // バッファを指定された文字セットでデコード
  return buffer.toString(charset);
}

そして、処理した結果は body に入ります。

console.log(body.files.length); // 添付ファイルの数
console.log(body.files[0].filename); // 1つ目の添付ファイルのファイル名
console.log(body.fields.server_composition); // pro

ファイルを保存する場合には、 content にデータが入っています。

console.log(body.files[0].content); // ファイルの内容(Buffer.concat(fileData)した内容)

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

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

Webhookログ

まとめ

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

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

受信サーバ | Customers Mail Cloud