【第2回】受信メールを解析して自動応答!Cloudflare Email Routing + postal-mime で文字化けのない自動返信システムを作ろう

typescript-resend-2 プログラミング

サーバーの維持費を0円(無料枠内)に抑えるモダンメールサーバー構築連載の第2回目です。

前回は、ResendとCloudflare Workersを連携させてローカル環境で「プログラムからメールを送信する」ところまでを実装しました。

今回は、独自ドメイン宛てに届いたメールをCloudflareでキャッチし、プログラム(Cloudflare Workers)で「誰から、どんな件名・本文で届いたか」を解析して、自動で返信を戻す仕組みを作っていきます。

メール受信のプログラムで最大の難所となる「文字化け対策」もクリアしていきます。

1. 今回実装する「受信・自動応答」のフロー

今回作成するプログラムの動きは以下の通りです。

  1. ユーザーが info@go-pro-world.net 宛てにメールを送信する
  2. Cloudflare Email Routing がメールを検知し、そのまま Workers へ転送する
  3. Workers(プログラム) が起動し、届いた生(Raw)データを解析する
  4. 解析した「送信元のメールアドレス」に向けて、Resend経由で自動応答メール(本文引用付き)を送信する

2. Cloudflareのメールルーティング設定

まずは、独自ドメインに届いたメールをWorkerに流し込むための設定をCloudflareの管理画面で行います。

設定手順

  • Cloudflareのダッシュボードにログインし、対象のドメインを選択します。
  • 左メニューの 「メールサービス」>「Email Routing」 > タブを開きます。
  • 右上の「ドメインをオンボード」のボタンを押して独自ドメインを選択します。
  • 「Email Routing」で先ほど追加した独自ドメインを選択します。
  • 「ルーティングルールタブ」を選択して、「ルールを作成」 ボタンをクリックし、以下のように入力します。
  • メール パターン: info (※独自ドメインの左側に info と入力)
  • アクション: Worker に送信
  • 宛先 Workers: 前回のコマンドで作成したWorkerを選択
  • 右下の「保存」をクリックします。

これで、info@独自ドメイン宛てのメールが、リアルタイムにあなたのプログラムへ転送されるようになります。

3. 必要なライブラリのインストール

メールのデータは、裏側では「MIME(マイム)」という非常に複雑な形式の長文テキスト(生のデータ)で届きます。

そこで、Cloudflare Workersでも軽量・高速に動くメール解析ライブラリ postal-mime をプロジェクトに導入します。

ターミナルでプロジェクトのフォルダを開き、以下のコマンドを実行してください。

npm install postal-mime

4. 受信・解析・自動応答プログラムの実装

それでは、src/index.ts のコードを丸ごと書き換えていきましょう。 今回は、Webからのリクエストを受ける fetch だけでなく、メール受信イベントを受け取る email というWorkers専用の機能を新しく追加します。

解説コメントを付けた完成版コードがこちらです。

import { Resend } from 'resend';
import PostalMime from 'postal-mime';

// 環境変数の型定義(前回のキーを引き続き使用します)
export interface Env {
  RESEND_API_KEY: string;
}

export default {
  // ■ 1. Web(API)からのリクエストを受け付ける関数(第1回で実装したもの)
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    if (request.method !== 'POST' || url.pathname !== '/send') {
      return new Response('Not Found', { status: 404 });
    }
    // (中略:前回の送信ロジックがそのまま動きます)
    return new Response('Fetch endpoint active', { status: 200 });
  },

  // ■ 2. 【今回新規追加】独自ドメイン宛てにメールが届いたときに自動実行される関数
  async email(message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext): Promise<void> {
    try {
      // ① Cloudflareから届いたメールの「生データ(Raw Email)」をストリームとして取得
      const rawEmailStream = message.raw;
      
      // ② postal-mime を使って、生データを解析(パース)する
      const parser = new PostalMime();
      const parsedEmail = await parser.parse(rawEmailStream);

      // ③ 解析データから「送信元」「件名」「本文」を抽出
      const fromAddress = message.from; // 送信元のメールアドレス
      const subject = parsedEmail.subject || 'No Subject'; // メールの件名
      const bodyText = parsedEmail.text || '本文なし'; // メールの本文(テキスト)

      // 日本語の文字化け(MIMEエンコード)を防ぐための「デコード(復元)」処理
      // ※件名や本文が `=?UTF-8?B?...=` のような謎の文字列になっているのを日本語に戻します
      const decodedSubject = decodeMimeHeader(subject);

      // ④ 自動応答のメール本文を作成(相手の本文を引用)
      const replyText = `メールを受け付けました。

件名: ${decodedSubject}

本文:
------------------------------------------
${bodyText}
------------------------------------------

※このメールはシステムより自動送信されています。`;

      // ⑤ Resendを使って、メールを送ってきた相手に自動返信を送信!
      const resend = new Resend(env.RESEND_API_KEY);
      await resend.emails.send({
        from: 'info@go-pro-world.net', // あなたの独自ドメインアドレス
        to: [fromAddress],             // メールの差出人(相手のアドレス)
        subject: `【自動応答】${decodedSubject}`,
        text: replyText,
      });

    } catch (error: any) {
      console.error('メール処理中にエラーが発生しました:', error.message);
      // 注意: ここでエラーを throw するとCloudflare側でメールが「一時不達」となり再送され続けます。
      // ログを出力して処理を正常に終了させるのが安全です。
    }
  }
};

/**
 * 【文字化け対策ヘルパー関数】
 * メールヘッダー(件名など)のMIMEエンコードを検知し、正しい日本語(UTF-8)にデコードします。
 */
function decodeMimeHeader(header: string): string {
  // =?UTF-8?B?BASE64文字列?= のパターンを検出する正規表現
  const mimeRegex = /=\?UTF-8\?B\?([^\?]+)\?=/gi;
  
  return header.replace(mimeRegex, (_, base64Str) => {
    try {
      // Base64をバイナリデータに変換し、TextDecoderで日本語文字列に戻す
      const binaryString = atob(base64Str);
      const len = binaryString.length;
      const bytes = new Uint8Array(len);
      for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
      }
      return new TextDecoder('utf-8').decode(bytes);
    } catch (e) {
      return header; // デコードに失敗した場合は元の文字列をそのまま返す
    }
  });
}

5. 本番へのデプロイと動作確認

ローカル環境ではメールの実際の「受信イベント」をテストするのが難しいため、一度Cloudflareの本番環境へデプロイ(公開)してテストします。

1. 本番用のAPIキーを登録する

今まではローカルでテストしていたため、APIキーを wrangler.jsonc 内の “vars” に書いていました。 本番のCloudflare環境にAPIキーを安全に保存するため、PowerShellで以下のコマンドを実行します。

※ Wranglerが起動したままになっている場合は、起動している方のウィンドウで Ctrl + C を押して一度終了させてから実行してください。

npx wrangler secret put RESEND_API_KEY

実行すると、ターミナルで ✔ Enter a secret value: とAPIキーの入力を求められます。 ここに ResendのAPIキー(re_xxxx…) を貼り付けて Enter を押してください。

2. クラウドにデプロイする

以下のコマンドを実行して、プログラムをCloudflareのサーバーにデプロイ(アップロード)します。

npm run deploy

デプロイが成功すると、画面に以下のようなURLが表示されます。

https://mail-serverless.<あなたのユーザー名>.workers.dev

これで、あなたのメールサーバープログラムが世界中からアクセスできる状態(サーバーレス)になりました。

実際にメールを送ってテストする

設定が完了したら、普段使っている個人用のGmailなどから、独自ドメインのアドレス宛てにテストメールを送信してみましょう。

送ったGmailの受信トレイに、「【自動応答】テストメール」 という件名で、自分の送った本文が綺麗に引用された返信メールが届くはずです。

まとめと次回予告

第2回目は、Cloudflare Workersの email ハンドラを活用し、受信したメールをリアルタイムに解析して自動返信するシステムを完成させました。

『プログラムからの自動返信はできたけれど、自分から普通に手動で返信したいときはどうするの?

そんな疑問を持つ方のために、次回(最終回)は、今回作った仕組みをそのまま活かし、「普段使っているGmailの画面から、独自ドメイン(info@…)のアドレスとして手動で送受信できるようにする連携設定」を解説します。

コメント

タイトルとURLをコピーしました