🐥
AWSでメール誤送信を防ぐ 3 層+α ガード
うっかり本番ユーザーにテストメールを送る事故を防ぐための調査メモ。
結論だけ欲しいなら TL;DR へ。
TL;DR
レイヤー | やること | 使うサービス |
---|---|---|
静的ガード | コードに残った 【TEST】 などを CI で grep してビルドを落とす |
GitHub Actions 等 |
リアルタイムガード | SES → SNS → Lambda で件名・タグを検査。NGならthrowして送信を失敗させる | SES / SNS / Lambda |
サーキットブレーカ | 1 分あたり送信数がしきい値を超えたらSES全体を自動停止 | CloudWatch Alarm → SNS → Lambda (ses:PutAccountSendingAttributes ) |
+α | 開発環境から本番SESキーを使えないようにする | IAM / Secrets Manager / AWS SSO |
コストは従量課金。月数円で済むはず
誤送信が起きる定番パターン
- テスト件名/本文を残したままデプロイ
- 開発PCやCIが本番のAWSキーを使う
- バグやループで短時間に大量メールを出す
3 層ガードの実装
1. 静的ガード
# .github/workflows/mail_guard.yml
- name: Grep TEST mails
run: |
grep -R --line-number "【TEST】" ./templates && exit 1 || echo "OK"
本番用ブランチだけに仕込もう
2. リアルタイムガード
- SES Configuration Set を作成し
send
イベントを SNS Topic へ。 - SNS → Lambda で件名や
Environment=staging
タグをチェック。 - 条件NGなら
throw new Error()
。
export const handler = async (event) => {
const msg = JSON.parse(event.Records[0].Sns.Message);
const subj = msg.mail.commonHeaders.subject ?? '(no subject)';
if (/【TEST】/i.test(subj)) {
await notifySlack('🚨 TEST メールをブロック', msg);
throw new Error('block');
}
await notifySlack('📧 正常送信', msg);
};
3. サーキットブレーカ
CloudWatchで Send
メトリクスを 1 分ごとに監視。50 通 / 分を超えたら送信停止。
import boto3
ses = boto3.client('sesv2')
def lambda_handler(event, context):
ses.put_account_sending_attributes(SendingEnabled=False)
復旧は手動で SendingEnabled=True
。
+α: 本番キーを開発で使わせない
ステップ | やること | 効果 |
---|---|---|
1 | prod / dev で AWSアカウントまたはIAMロールを分割 | キーを物理的に渡せない |
2 | IAM条件付きポリシーで Environment=prod 以外は ses:SendEmail を拒否 |
キーをコピーしてもAPIが403 |
3 | Secrets Manager を環境別に管理し、GitHub環境保護ルールで dev ジョブに prod シークレットを注入禁止 | CI 渡さない / 受け取らない |
アプリ側でできること
- Mail ドライバをスタブ化 — 非本番ならMailhog/Mailtrap。
-
テストドメインで即 return —
if (to.endsWith('@test.example.com')) return;
。 -
件名プリフィックス自動付与 — 非本番なら
★STG★
や【Local】
を件名先頭に。 -
テンプレート Lint —
dummy@example.com
やTODO:
が残っていないか Pre‑commit でチェック。 - 送信リハーサル — 本番と同量をMailtrapに送り、OKならフラグ変更で本番送信。
Discussion