Amazon SESでメールを一括送信したい時の留意点

2022.02.02

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、Amazon SESでメールを一括送信したい時の留意点についてです。

留意点

一括送信にはテンプレートを使用する必要がある

まず、SESでメール一括送信をする際にはSendBulkTemplatedEmailAPIを使用する必要があります。

これにより1回のAPI実行で複数通のメールを送信することができます。SendBulkTemplatedEmailであればSendEmailSendRawEmailなどのAPIで1通ずつ送信する場合に比べて、送信し終わるまでの所要時間やAPI料金を節約することができます。

ここで留意点として、API名にTemplatedとある通りSendBulkTemplatedEmailはSES上に作成されたテンプレートを使用したメール送信となります。

よってSendBulkTemplatedEmail実行時にはSES上にテンプレートが作成されている必要があります。テンプレート作成はCreateTemplateAPIで行えます。

下記はSES上に作成済みのテンプレートを取得した様子です。メールの件名(SubjectPart)と本文(TextPartおよびHtmlPart)の{{}}で囲まれた変数をSendBulkTemplatedEmailを叩く際に指定して置き換えます。これにより送信先に応じた内容のメールを簡単に送付できるようになっています。

$ aws ses get-template --template-name TestTemplate01
{
    "Template": {
        "TemplateName": "TestTemplate01",
        "SubjectPart": "こんにちは {{name}}!",
        "TextPart": "前略 {{name}}さん\r\nあなたの好きなAWSサービスは{{favoriteservice}}ですね。\r\n草々",
        "HtmlPart": "<h1>こんにちは {{name}}さん</h1><p>あなたの好きなAWSサービスは{{favoriteservice}}ですね。</p>"
    }
}

それでは送信先に応じてメールの内容を変える必要がない一括送信の場合にテンプレートを使わずに送信できないのか?例えば「SendBulkEmail」なるものは無いのか?という疑問が湧いてきますが残念ながら無いようです。SESのAPI一覧でSend***という名称を洗ってみてもそれらしきものは現時点で見当たりませんでした。

なので一括送信するメールの内容が頻繁に変わり、同じテンプレートを毎回使えない場合は、次のような方法を取る必要があります。

  1. 一括送信前にテンプレートを作成し、送信後に削除する方法(使い捨て)
  2. 一括送信時前に予め作成されたテンプレートを更新する(使いまわし)

おすすめの方法は1です。方法1であれば、作成するテンプレートの名前(TemplateName)をユニークなID(UUIDなど)にすることにより、万が一同タイミングで別々の一括送信処理が実施されても、不意にテンプレートが上書きされてしまう事故も防げます。

方法1は以下の記事でも紹介されているので参考にしてみてください。

SendBulkTemplatedEmailの一括送信時の制限

ここまでで紹介したSendBulkTemplatedEmailですが、メール送信時に次のような制限があるので留意する必要があります。

  • 1回のAPI実行でのメッセージ送信可能数は最大50通

Destinations.member.N
One or more Destination objects. All of the recipients in a Destination receive the same version of the email. You can specify up to 50 Destination objects within a Destinations array.

宛先配列内で最大50の宛先オブジェクトを指定できます。

  • 1通のメッセージ内での指定可能受信者数はTo、CC、BCC合わせて最大50

The message may not include more than 50 recipients, across the To:, CC: and BCC: fields. If you need to send an email message to a larger audience, you can divide your recipient list into groups of 50 or fewer, and then call the SendBulkTemplatedEmail operation several times to send the message to each group.

メッセージには、To:、CC :、およびBCC:フィールド全体で50を超える受信者を含めることはできません。より多くのオーディエンスに電子メールメッセージを送信する必要がある場合は、受信者リストを50以下のグループに分割し、SendBulkTemplatedEmail操作を数回呼び出して各グループにメッセージを送信できます。

もしこの制限を超える場合はメッセージの分割や、APIを複数回に分けて叩く必要があります。

この制限について実際に確認してみます。次のようなAWS SDK for JavaScriptでsendBulkTemplatedEmailを実行するコードで試してみます。引数でメッセージ数とメッセージ毎の受信者を指定できるようにしています。

src/lambda/handlers/sendBulkMail.ts

import { SES } from 'aws-sdk';
import { v4 as uuidv4 } from 'uuid';

const SES_API_VERSION = '2012-10-08';
const REGION = 'ap-northeast-1';

export const sesClient = new SES({
  region: REGION,
  apiVersion: SES_API_VERSION,
});

interface SendBulkEmailRequestParameter {
  SubjectPart: string;
  TextPart: string;
  toAddresses: string[][];
  fromAddress: string;
}

export const sendBulkEmail = async (
  sendBulkEmailRequestParameter: SendBulkEmailRequestParameter
): Promise<void> => {
  //メールテンプレート名をUUIDで作成(重複回避のため)
  const templateName = uuidv4();

  const destinations = sendBulkEmailRequestParameter.toAddresses.map((d) => ({
    Destination: {
      ToAddresses: d,
    },
  }));

  //一括送信用のメールテンプレート作成
  await sesClient
    .createTemplate({
      Template: {
        TemplateName: templateName,
        SubjectPart: sendBulkEmailRequestParameter.SubjectPart,
        TextPart: sendBulkEmailRequestParameter.TextPart,
      },
    })
    .promise();

  //メールを一括送信
  await sesClient
    .sendBulkTemplatedEmail({
      Source: sendBulkEmailRequestParameter.fromAddress,
      Template: templateName,
      Destinations: destinations,
      DefaultTemplateData: JSON.stringify({}),
    })
    .promise();

  //一括送信用のメールテンプレート削除
  await sesClient
    .deleteTemplate({
      TemplateName: templateName,
    })
    .promise();
};

まずTo2個のメッセージが30通の場合。メッセージ数も受信者数も制限範囲内です。

import { sendBulkEmail } from '../src/lambda/handlers/sendBulkMail';

const FROM_DOMAIN = process.env.FROM_DOMAIN!;
const TO_ADDRESS = process.env.TO_ADDRESS!;

describe('sendBulkEmail', () => {
  test('To2個が30通の場合', async (): Promise<void> => {
    const toAddresses = [];
    for (let i = 0; i < 30; i++) {
      toAddresses.push([TO_ADDRESS, TO_ADDRESS]);
    }
    await sendBulkEmail({
      SubjectPart: 'クーポンのお知らせ',
      TextPart: 'クーポン番号は○○○です。',
      toAddresses: toAddresses,
      fromAddress: `system@${FROM_DOMAIN}`,
    });
  });
});

実行すると、成功しました。

$  npx jest
 PASS  test/sampleHandler.test.ts (6.997 s)
  sendBulkEmail
    ✓ To2個が30通の場合 (818 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        7.044 s, estimated 8 s
Ran all test suites.

宛先にも同じメールが30通届きました。

次にTo1個のメッセージが51通の場合。メッセージ数が制限を超過しています。

import { sendBulkEmail } from '../src/lambda/handlers/sendBulkMail';

const FROM_DOMAIN = process.env.FROM_DOMAIN!;
const TO_ADDRESS = process.env.TO_ADDRESS!;

describe('sendBulkEmail', () => {
  test('To1個が51通の場合', async (): Promise<void> => {
    const toAddresses = [];
    for (let i = 0; i < 51; i++) {
      toAddresses.push([TO_ADDRESS]);
    }
    await sendBulkEmail({
      SubjectPart: 'クーポンのお知らせ',
      TextPart: 'クーポン番号は○○○です。',
      toAddresses: toAddresses,
      fromAddress: `system@${FROM_DOMAIN}`,
    });
  });
});

実行すると、想定通りエラーとなりました。destinationを50以内にしろと怒られています。

$  npx jest
 FAIL  test/sampleHandler.test.ts (6.951 s)
  sendBulkEmail
    ✕ To1個が51通の場合 (324 ms)

  ● sendBulkEmail › To1個が51通の場合

    InvalidParameterValue: You must specify at least 1 destination, and no more than 50 destinations

次にTo51個のメッセージが1通の場合。受信者数が制限を超過しています。

import { sendBulkEmail } from '../src/lambda/handlers/sendBulkMail';

const FROM_DOMAIN = process.env.FROM_DOMAIN!;
const TO_ADDRESS = process.env.TO_ADDRESS!;

describe('sendBulkEmail', () => {
  test('To1個が51通の場合', async (): Promise<void> => {
    const toAddresses = [];
    for (let i = 0; i < 51; i++) {
      toAddresses.push(TO_ADDRESS);
    }
    await sendBulkEmail({
      SubjectPart: 'クーポンのお知らせ',
      TextPart: 'クーポン番号は○○○です。',
      toAddresses: [toAddresses],
      fromAddress: `system@${FROM_DOMAIN}`,
    });
  });
});

実行すると、あれ、エラーとならず実行が成功してしまいました。

$  npx jest
 PASS  test/sampleHandler.test.ts (6.756 s)
  sendBulkEmail
    ✓ To51個が1通の場合 (605 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        6.802 s
Ran all test suites.

しかししばらく待ってもメールは宛先に届きませんでした。クライアント側ではエラーとならず、SESのサーバー側で送信が抑制されているような動きですね。

おわりに

Amazon SESでメールを一括送信したい時の留意点についてでした。

紹介した仕様や制限に気をつけながら便利に使いたいですね。また下記の2エントリもとても参考になるので合わせてお読みください。

参考

以上