🐻

【AWS SES, Lambda, API Gateway】実装編:サーバーレスでメール配信APIを作る

に公開

はじめに

Webサービスやアプリケーション開発において、メール配信機能はとても重要です!例えば、以下のようなユースケースが考えられます。

  • ユーザー登録時の確認メール送信
  • パスワードリセットの案内メール送信
  • ニュースレターやお知らせの配信
  • お問い合わせへの自動返信
  • システムからのアラート通知

AWS SESとLambda、API Gatewayを活用すれば、これらのメール配信機能をサーバーレスで簡単に実装できます!
この記事では、具体的な実装手順を通して、AWSのサーバーレスサービスによる効率的なメール配信システムの構築方法を解説します。

設計編はこちら↓
https://zenn.dev/zenzoe/articles/ce5391f125f46b

前提条件

  • AWSアカウントを持っていること

2. SESの初期設定

まず、SESの初期設定が必要です。
AWS SESのサービスページに遷移して、以下の内容をセットアップしていきましょう。

  • メールアドレスを検証
  • 送信ドメインを検証
  • 本番アクセスをリクエスト

2-1. メールアドレスの検証

SESコンソールから、送信元として使用するメールアドレスを登録・検証します。確認メールが届くので、メール内のリンクをクリックして検証を完了します。

2-2. 送信ドメインを検証

SESを通してメールを送信するには、送信元のドメインが必要です。Amazon Route53などのドメイン取得サービスを用いてドメインを取得します。

2-3. 本番アクセスをリクエスト

初期状態では、送信できるメール数や送信先アドレスに制限があります。
AWS SESのサンドボックス環境では、検証済みのメールアドレスにしかメールを送信できません。
本番環境で運用したい場合は、AWSサポートに送信制限の解除を申請してください。

3. Lambda関数の実装

3-1. 必要なIAMロールの設定

Lambda関数がSESを利用するためには、AWSのリソースにアクセスするための権限を設定する必要があります。この権限はIAMポリシーを通じて付与されます。

以下の手順で、Lambda関数がSESを利用するために必要なIAMロールを設定します。

  1. IAMコンソールを開く: AWSマネジメントコンソールでIAMのサービスページを開きます。
  2. 新しいロールを作成: 「ロール」-> 「ロールの作成」を選択します。
  3. ロールの種類を選択: 「AWSサービス」を選択し、ユースケースで「Lambda」を選択します。
  4. アクセス権限を設定: ポリシーをアタッチします。「AmazonSESFullAccess」ポリシーを検索してアタッチします。「確認」をクリックします。
  5. ロール名と説明を入力: ロール名(例:lambda-ses-role)と説明を入力し、「ロールの作成」をクリックします。

3-2. Lambda関数を作成

  1. Lambdaコンソールを開く: AWSマネジメントコンソールでLambdaのサービスページを開きます。
  2. 関数の作成: 「関数の作成」ボタンをクリックします。「一から作成」のオプションを選択します。
  3. 基本情報の設定:
    • 関数名: 任意の名前(例: send-email-api)を入力します。
    • ランタイム: Node.js 22.x を選択します。
    • アーキテクチャ: x86_64 を選択します。
    • アクセス権限: 実行ロールで「既存のロールを使用する」を選択し、先ほど作成したIAMロール(例: lambda-ses-role)を選択します。
  4. 「関数の作成」をクリックします。

3-3. メール送信機能の実装

Lambda関数に以下のコードを記述します。

import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";

const ses = new SESClient({ region: "ap-southeast-2" }); // リージョンは適宜変更

export const handler = async (event) => {

  let requestBody;

  try {
    if ((event.body) && (typeof event.body === 'string')){
      requestBody = JSON.parse(event.body);
    } else {
      requestBody = event;
    }
  } catch (error) {
    console.error("リクエストボディのパースエラー:", error);
    return { statusCode: 400, body: JSON.stringify({ error: "不正なリクエストボディです" }) };
  }

  const { to, subject, messageBody } = requestBody; // messageBodyを使用

  // 入力値検証
  const missingFields = [];
  if (!to) missingFields.push("to");
  if (!subject) missingFields.push("subject");
  if (!messageBody) missingFields.push("messageBody");

  if (missingFields.length > 0) {
    return { 
      statusCode: 400,  
      body: JSON.stringify({ 
        error: "必須項目が不足しています", 
        missing_fields: missingFields // 不足しているフィールド名を配列で返す
      }) 
    };
  }

  const toAddresses = Array.isArray(to) ? to : [to]; // to を配列に変換
  const sourceEmailAddress = process.env.FROM_EMAIL; // 環境変数名を FROM_EMAIL に変更

  const command = new SendEmailCommand({
    Destination: { ToAddresses: toAddresses },
    Message: {
      Body: { Text: { Data: messageBody } }, // HTMLメールの場合は Html: { Data: body } を使用
      Subject: { Data: subject },
    },
    Source: sourceEmailAddress,
  });

  try {
    const response = await ses.send(command);
    console.log("メール送信成功:", response);
    return { statusCode: 200, body: JSON.stringify({ message: "メールを送信しました", messageId: response.MessageId }) };
  } catch (error) {
    console.error("メール送信失敗:", error);
    return { statusCode: 500, body: JSON.stringify({ message: "メール送信に失敗しました", error: error.message }) }; // エラー内容を返す
  }
};

環境変数の設定手順

  1. 設定タブを選択: 「設定」タブをクリックします。
  2. 環境変数を追加:
    • 「環境変数」セクションで「環境変数を追加」をクリック。
    • キーFROM_EMAILに SESに登録したドメインに基づくメールアドレスを入力します。
  3. 保存: 変更を保存します。

3-4. Lambda関数内でテストする

  1. テストイベントの設定:「テスト」タブを選択し、「新しいイベントを設定」をクリックします。

  2. イベントJSONの編集: イベントJSONを以下のように編集します。to, subject, messageBody にはテスト用の値を入力してください。to には配列で複数のメールアドレスを指定することも可能です。

{
  "to": ["example1@gmail.com", "example2@gmail.com"],
  "subject": "テストメール",
  "messageBody": "これはLambdaを通じて送信されたテストメールです。"
}

⚠️ 注意: 送信先のメールアドレスは適宜変更してください。

  1. テストの実行: イベントJSONを編集後、「保存」をクリックし、次に「テスト」ボタンをクリックします。

  2. 結果の確認: 実行結果が表示されます。statusCode が 200 であれば成功です。ログ出力も確認し、エラーが発生していないか確認しましょう。

4. API Gateway を利用したAPI構築

API Gateway を利用することで、Lambda 関数をよりセキュアかつ高機能な API として公開できます。ここでは、API Gateway を利用したAPI構築の手順と、テスト方法について解説します。

4-1. API Gateway の設定

  1. API Gateway のコンソールを開く: 新しい REST API を作成します。
  2. API 名を指定: (例: send-email-api
  3. リソースを作成: (例: /send)このとき、「CORS (クロスオリジンリソース共有)」にチェックを入れます。
  4. POST メソッドを作成: Lambda 関数との統合を設定します。統合対象には、3章で作成した Lambda 関数を指定します。
  5. CORS を有効化: 作成したリソースから「CORS を有効化」を押し、「Access-Control-Allow-Methods」で「POST」を選択し、使用したいアプリのオリジンを許可するように設定してください。
  6. API をデプロイ: ステージ名(例: dev)を指定します。

4-2. API キーの作成 (推奨)

API キーを作成し、API へのアクセスを制限することで、セキュリティを強化し、API の使用量を制御できます。

  1. 使用量プランを作成: API Gateway のコンソールから使用量プランを作成します。
  2. API キーを作成: API Gateway のコンソールからAPI キーを作成します。
  3. API キーを使用料プランに追加: 作成したAPI キーのページから「使用料プランに追加」で関連付けます。
  4. API キーの使用を必須に設定: リソースのPOSTメソッドでメソッドリクエストの設定の「編集」から、「API キーは必須です」にチェックを入れます。
  5. API を再デプロイ: API を再デプロイして更新を有効にします。
  6. 使用料プランと紐づけ: 作成した使用料プランのページから「ステージを追加」でデプロイしたステージを関連付けます。

また、Cognitoと連携することでAPIの利用制限をかけることもできます。

4-3. APIのテスト

API Gateway のコンソールから、作成した API をテストできます。

  1. 作成したAPIの「POST /send」メソッドを選択します。
  2. 「テスト」をクリックします。
  3. リクエストボディに、下記のようなJSONを入力します。
{
  "to": ["example1@gmail.com", "example2@gmail.com"],
  "subject": "テストメール",
  "messageBody": "これはAPI Gatewayを通じて送信されたテストメールです。"
}

5. Reactアプリからの呼び出しテスト

5-1. Reactアプリの修正

Axiosライブラリを使ってReactアプリからAPI Gateway経由でLambda関数を呼び出し、メール送信機能をテストします。
Axiosを使ってAPIを呼び出すReactアプリのコンポーネントを作成します。

import React, { useState } from 'react';
import axios from 'axios';

const EmailSender = ({ apiUrl, apiKey }) => {
  const [email, setEmail] = useState('');
  const [subject, setSubject] = useState('');
  const [messageBody, setMessageBody] = useState('');
  const [messageId, setMessageId] = useState('');
  const [error, setError] = useState('');
  const [message, setMessage] = useState('');

  const handleSubmit = async () => {
    const emailArray = email.split(',').map(e => e.trim()); // カンマ区切りで分割し、トリムする
    try {
      const response = await axios.post(
        apiUrl,
        { 
          to: emailArray, // 配列として指定
          subject: subject, // subjectを直接指定
          messageBody: messageBody // messageBodyを直接指定
        }, 
        {
          headers: {
            'Content-Type': 'application/json',
            'x-api-key': apiKey // APIキーをプロパティから取得
          }
        }
      );

      console.log("sending email:", response);

      // response.data.bodyをJSONとしてパース
      const responseBody = JSON.parse(response.data.body); // JSONパース

      // パースしたデータからmessageIdとmessageを取得
      setMessageId(responseBody.messageId);
      setMessage(responseBody.message); // メッセージを取得してステートに設定
      setError('');
    } catch (error) {
      console.error("Error sending email:", error);
      setError(error.response?.data?.error || 'メール送信に失敗しました'); // エラーメッセージを抽出
      setMessageId('');
      setMessage('');
    }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
      <input
        type="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="送信先メールアドレス"
      />
      <input
        type="text"
        value={subject}
        onChange={e => setSubject(e.target.value)}
        placeholder="件名"
      />
      <textarea
        value={messageBody}
        onChange={e => setMessageBody(e.target.value)}
        placeholder="本文"
      />
      <button 
        onClick={handleSubmit} 
        style={{ alignSelf: 'flex-start' }} // ボタンの幅を自動に設定
      >
        メール送信
      </button>
      {messageId && <p>メッセージID: {messageId}</p>}
      {message && <p>メッセージ: {message}</p>}
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
};

export default EmailSender;

呼び出し箇所

EmailSender コンポーネントを使用するには、まず必要なプロパティ(API GatewayのURLとAPIキー)を指定して呼び出します。以下のコードは、App コンポーネント内で EmailSender を呼び出す例です。

import React from 'react';
import EmailSender from './EmailSender'; // EmailSenderコンポーネントをインポート

function App() {
  return (
    <div>
      <h1>メール送信アプリ</h1>
      <EmailSender 
        apiUrl="YOUR_API_GATEWAY_URL" // API GatewayのURLを指定
        apiKey="YOUR_API_KEY" // APIキーを指定
      />
    </div>
  );
}

export default App;

この例では、App コンポーネント内に EmailSender コンポーネントを配置しています。apiUrlapiKey の値を適切に設定することで、メール送信機能を持つフォームが表示されます。ユーザーがメールアドレス、件名、本文を入力し、「メール送信」ボタンをクリックすると、指定されたAPI Gatewayを通じてメールが送信されます。

CORS (Cross-Origin Resource Sharing) について

CORSは、異なるオリジン(ドメイン、プロトコル、ポート)を持つWebページからのリソースへのアクセスを制御するセキュリティメカニズムです。ReactアプリとAPI GatewayのURLが異なるオリジンを持つ場合、CORSの設定が正しく行われていないと、ブラウザがAPIリクエストをブロックします。

API GatewayでCORSを有効化し、Reactアプリのオリジンを許可するように設定することで、この問題を解決できます。設定方法は4章で説明した通りです。

5-2. テストの実行

  1. Reactアプリを起動します。
  2. メールアドレス、件名、本文を入力し、送信ボタンをクリックします。
  3. メールが送信されること、そしてReactアプリにエラーメッセージが表示されないことを確認します。
  4. 必要に応じて、ブラウザの開発者ツールでネットワークリクエストを確認し、レスポンスヘッダーに Access-Control-Allow-Origin が含まれていることを確認します。


Reactからメール送信APIを利用してメールを送信することができました!
これで、Axiosを使ったReactアプリからのメール送信テストが完了です。

まとめ

この記事では、AWS SESとLambdaを使用して、スケーラブルでコスト効率の良いメール配信APIを構築する方法について解説しました。

参考資料

お疲れ様でした!

この記事は以上になります。お疲れさまでした。

Discussion