📫

SES サプレッションリストを一括CSVダウンロード

2023/07/18に公開

AWS でメールの送信でよく利用するサービス Amazon SES で、ハードバウンス(=宛先不明などの理由で配送できないと判断されたメール)の管理としてサプレッションリストが使われます。

このサプレッションリストの一覧に登録されているメールアドレスは到達不能としてマークされている状態なので、SES から送信されることはありません。

サプレッションリストは検索できない!?

メール送信を管理する上で「特定のメールアドレスがリストに含まれていないか」を調査することがあります。ところが AWS コンソールからは一切の検索ができないのです。できることはページ送りのみです(2023年7月時点)。

メールアドレスやドメインを指定した検索ができないのは痛いです。せめて CSV 一括ダウンロードできれば・・・。

ということで、ザクっと作ってみました!

CSV 形式で書き出すサンプル

SESv2 の ListSuppressedDestinations API を利用すれば取得できそうです。

全て取得するには NextToken パラメーターを利用してページング処理も必須になります。

今回は TypeScript を使います。対応する SDK は以下になります。

実行環境を作る

以下のコマンドを参考に新しいディレクトリを用意して、必要なパッケージを準備します。

mkdir ses-list-suppressed-emails
cd ses-list-suppressed-emails

npm init -y
npm i typescript ts-node @types/node @aws-sdk/client-sesv2

ソース

同じディレクトリに index.ts ファイルとして以下をコピペします。

※コメント CAHNGE SES REGION ある所は、利用しているリージョンに書き換えます。

index.ts
import {
  ListSuppressedDestinationsCommand, SESv2Client, SuppressedDestinationSummary,
} from "@aws-sdk/client-sesv2";
import { appendFileSync, writeFileSync } from "fs";

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

function writeCsvHeader(filename: string) {
  writeFileSync(
    filename,
    '"EmailAddress","Domain","Reason","LastUpdateTime"\r\n',
    { encoding: "utf8" }
  );
}

function writeCsv(filename: string, summaries: SuppressedDestinationSummary[]) {
  // Convert to CSV
  const lines = summaries.map<string>((summary) => {
    const lastUpdateTime = summary.LastUpdateTime?.toISOString() ?? "";
    const email = summary.EmailAddress ?? "";
    const domain = email.split("@")[1] ?? "";
    return `"${email}","${domain}","${summary.Reason}","${lastUpdateTime}"`;
  });

  appendFileSync(filename, lines.join("\r\n") + "\r\n", { encoding: "utf8" });
}

async function main() {
  const filename = "suppressed-emails.csv";

  // --- CAHNGE SES REGION ---
  const client = new SESv2Client({ region: "us-east-1" });

  writeCsvHeader(filename);
  let nextToken: string | undefined;
  do {
    console.log("Fetching...");

    const command = new ListSuppressedDestinationsCommand({
      PageSize: 100,
      NextToken: nextToken,
    });
    const response = await client.send(command);

    if (response.SuppressedDestinationSummaries) {
      writeCsv(filename, response.SuppressedDestinationSummaries);
    }
    // WAIT 1.5s
    await wait(1500);

    nextToken = response.NextToken;
  } while (nextToken);
}

main().catch((err) => console.error(err));

package.json

スクリプトに start を追加して直接 TypeScript を実行できるようにます。

package.json
  // ...省略...
  "scripts": {
    "start": "ts-node index.ts"
  },
  // ...省略...

実行する

npm start

しばらく待つと suppressed-emails.csv ファイルが出来上がります。

suppressed-emails.csv
"EmailAddress","Domain","Reason","LastUpdateTime"
"ta..ro@example.com","example.com","BOUNCE","2023-07-07T12:34:56.000Z"
"foo-o-bar@example.com","example.com","COMPLAINT","2023-07-07T12:34:56.000Z"

「あるドメインに含まれているアドレスすべて」の絞り込みもしやすいよう、別の列に分けてだしてみました。あとはローカルで検索や絞り込みなど自由にできます。

API レート制限に注意

API の呼び出し速度に制限があります。無制限で呼び出すとクオーターに引っかかり実行途中で拒否されるので注意が必要です(1敗)。

抜粋:

Amazon SES API アクションを呼び出すことができるレート
すべてのアクション(SendEmail、SendRawEmail、および SendTemplatedEmail を除く)は、1 秒あたり 1 つのリクエストに調整されます。

注意すべきは ListSuppressedDestinations を含む非送信系の API 呼び出しは纏めてカウントされてる点です。制限に掛からないよう余裕をもった対策をして既存システムに影響がでないよう配慮が必要です。

まとめ

大量のアドレスが溜まりやすいサプレッションリストを見直したり、調査する助けになれば幸いです。

それではまた!

コラボスタイル Developers

Discussion