[AWS] SESとLambda関数でメールの一斉送信を実装してみた

2020.05.31

Amazon Simple Email Service (Amazon SES) は、Amazonが提供するフルマネージド型のEメール配信サービスです。初期費用不要&従量課金制の料金体系で、基本的にはメールの送信数でコストが決まります。また、高可用性およびスケーラビリティなサービスなので、大量のメールを配信するケースにも対応できます。

本記事ではSESを利用して会員サイトのユーザーにメールを一斉送信する機能を実装してみます。

メール一斉送信

SESで送信元メールアドレスを登録

送信元のメールアドレスをSESに登録します。サンドボックス環境の場合は送信先のメールアドレスも登録しておきます。承認されていないメールアドレスに対してメールを送信する場合、サンドボックス環境の解除を申請する必要があります。

AWS SES メールアドレス登録

DynamoDBでユーザーテーブルを作成

メール送信先のユーザーリストを管理するテーブルを作成します。

AWS DynamoDB ユーザーテーブル

ユーザーテーブルに送信先のユーザーを登録します。

AWS DynamoDB ユーザーリスト

 

userId(PK) name emailAddress
a06b7c45-1303-45bf-a2d7-fec53fbc224c 田中太郎 takahashi.yudai+test1@*****.jp
1d484776-e2ee-40b6-a167-1f82603a14d8 鈴木一郎 takahashi.yudai+test1@*****.jp
cf57a613-c01c-4bd2-bf78-fe1f7a1ba920 山田花子 takahashi.yudai+test1@*****.jp

メール配信のLambda関数を実装

メール配信用のLambda関数を実装します。送信先のリストが多い場合、Lambdaのメモリとタイムアウトの設定に注意してください。また、Lambda関数を実装する際、DynamoDBとSESの権限が必要になります。

index.js

const DynamoDB = require('aws-sdk/clients/dynamodb');
const SES = require('aws-sdk/clients/ses');

const USER_TABLE_NAME = 'User';

const dynamodbDocumentClient = new DynamoDB.DocumentClient({
  apiVersion: '2012-10-08',
  region: 'ap-northeast-1',
});

const sesClient = new SES({
  signatureVersion: 'v4',
  region: 'us-east-1',
});

const templateName = 'MyTemplate'; // メールのテンプレート名
const source = 'takahashi.yudai@classmethod.jp'; // 送信元メールアドレス
const emailSubject = 'テストメール'; // メール件名
const emailText = '{{name}}様\r\n\r\nこれはテストメールです。'; // メール本文

/**
 * ユーザーリストを取得
 */
const getUserItemList = async () => {
  const params = {
    TableName: USER_TABLE_NAME,
  };

  let userItemList = [];

  do {
    const response = await dynamodbDocumentClient.scan(params).promise();

    params.ExclusiveStartKey = response.LastEvaluatedKey;

    userItemList = userItemList.concat(response.Items);
  } while (params.ExclusiveStartKey);

  return userItemList;
};

/**
 * 配列を指定した個数で分割
 * @param array array
 * @param number size
 */
const arrayChunk = (array, size) => {
  const chunks = [];

  while (0 < array.length) {
    chunks.push(array.splice(0, size));
  }

  return chunks;
};

exports.handler = async () => {
  const userItemList = await getUserItemList();

  const destinations = userItemList.map(userItem => {
    return {
      Destination: {
        ToAddresses: [userItem.emailAddress],
      },
      ReplacementTemplateData: JSON.stringify({ name: userItem.name }),
    };
  });

  const destinationChunks = arrayChunk(destinations, 50);

  await sesClient
    .createTemplate({
      Template: {
        TemplateName: templateName,
        SubjectPart: emailSubject,
        TextPart: emailText,
      },
    })
    .promise();

  for (let i = 0; i < destinationChunks.length; i++) {
    await sesClient
      .sendBulkTemplatedEmail({
        Source: source,
        Template: templateName,
        Destinations: destinationChunks[i],
        DefaultTemplateData: JSON.stringify({
          name: '名無し',
        }),
      })
      .promise();
  }

  await sesClient
    .deleteTemplate({
      TemplateName: templateName,
    })
    .promise();
};
  1. DynamoDBのユーザーテーブルからユーザーリストを取得
  2. ユーザーリストで宛先リストを作成
  3. 宛先リストを50個ずつ分割(最大50の宛先までリクエスト可能なので)
  4. メールのテンプレートを登録
  5. メールのテンプレートと宛先リストでメールを配信
  6. メールのテンプレートを削除

SESのレートリミットの都合上、メール送信の部分は意図的に並列処理を避けています。ReplacementTemplateDataで件名や本文のタグを置換できます。

動作確認

Lambda関数を実行してメールが送信されることを確認します。

SESメール受信

まとめ

SendBulkTemplatedEmailオペレーションを利用して簡単にメールの一斉送信を実装することができました。メールの件名や本文にタグを埋め込める機能はとても便利だと思います。

メールの送信先があまりにも多い場合には、Lambdaの実行時間制限である15分を超過してしまうかもしれません。また、メールの配信スピードを向上させる場合など、複数のLambdaで並行してメール送信を実行するケースも考えられます。その際にはSESのレートリミットを考慮して設計する必要があると思いました。

参考資料