SES の通知メールが迷惑メール判定されたので送信専用サブドメイン + DKIM で解消した話
社内システムから Lambda + SES で送っているログイン用通知メール(マジックリンク)が迷惑メール判定されるようになったので、送信専用サブドメインの SES Domain Identity + Easy DKIM + Custom MAIL FROM + DMARC で解消しました。
親ゾーンが別アカウント・別リポジトリ管理という制約下での NS 委譲の設計と、Terraform での段階移行の進め方を書きます。
問題
当初の SES 構成は以下でした。
- 送信元がメールアドレス単位の
aws_ses_email_identity(個人アドレス) - DKIM 署名なし
- SPF 不一致(MAIL FROM が amazonses.com のままで From ドメインとアライメントしない)
この状態では受信側の判定材料が弱く、迷惑メールフォルダ行きになっていました。
余談: SES の IAM 認可は宛先 Identity にも掛かる
迷惑メール以前に、SES 送信自体が AccessDenied で落ちる問題も踏んでいました。ses:SendEmail の IAM リソースを送信元 Identity のみに限定していたのですが、宛先がアカウント内の検証済み Identity の場合、AWS は宛先 Identity に対しても認可チェックを行います(実機ログ ... is not authorized to perform 'ses:SendEmail' on resource 'identity/<宛先email>' で確認)。
1 | # SES は From 側 Identity だけでなく、宛先がアカウント内の検証済み Identity の |
解決方針
送信専用サブドメイン(例: myapp.example.co.jp)の Domain Identity に切り替え、認証の3点セットを揃えます。
| 対策 | 内容 |
|---|---|
| DKIM | Easy DKIM(CNAME × 3) |
| SPF アライメント | Custom MAIL FROM ドメイン(bounce. サブドメイン + MX + SPF) |
| DMARC | p=none で開始し、DKIM/SPF の成立を確認してから段階的に引き上げ |
ここで制約になったのが、親ゾーン(example.co.jp)が別 AWS アカウント・別リポジトリで管理されていることです。サブドメインの Hosted Zone はアプリ側リポジトリで作り、親ゾーンから NS 委譲してもらう必要があります。
NS 委譲の設計: stg は prd ゾーン内で委譲する
環境は stg / prd の2つあります。素直にやると親ゾーンに NS レコードを2件(prd 用・stg 用)追加することになりますが、最終的に以下のチェーンにしました。
1 | example.co.jp(別アカウント・全社DNSリポジトリ) |
全社 DNS リポジトリへの依存を prd の 1 レコードに最小化し、stg の委譲は prd の Hosted Zone 内で完結させます。stg 環境を作り直しても全社リポジトリに影響せず、ライフサイクルがアプリのリポジトリ内に閉じるのが利点です。
NS レコードの値はハードコードせず、terraform_remote_state でアプリ側の output を参照します。
1 | # アプリ側 prd: stg サブドメインをこのゾーン内で委譲する |
Terraform 実装と段階移行
親ゾーンの NS 委譲が完了するまでドメイン検証は通らないため、「リソース作成」と「検証 + 切り替え」を PR 2段階に分割しました。同一 apply で検証まで進めると、委譲未完了で検証タイムアウト + 送信断のリスクがあります。
Step 1: SES + DNS リソースの先行作成(from は切り替えない)
1 | resource "aws_ses_domain_identity" "mail_from" { |
DNS 側は Hosted Zone + 検証 TXT + DKIM CNAME×3 + MAIL FROM の MX/SPF + DMARC です。
1 | # NOTE: Easy DKIM 署名検証用 CNAME レコード(3件)。dkim_tokens は新規作成時は apply 後に |
NS 委譲用に name servers を output で公開しておきます。
1 | output "mail_zone_name_servers" { |
Step 2: NS 委譲完了後に検証 + from 切り替え
NS 委譲が済んでから aws_ses_domain_identity_verification を追加します(委譲前に入れると apply がタイムアウトします)。
1 | # NOTE: ドメイン所有権検証の完了を待つ。NS 委譲済みの TXT レコードが |
検証が通ってから from アドレスを noreply@myapp.example.co.jp に切り替え、旧 aws_ses_email_identity を削除しました。旧 Identity は検証完了まで残しておくのが安全です。
細かい罠
dkim_tokensは apply 後にしか値が確定しないためfor_each + tosetのキーにできず、常に3件前提のcount = 3で書く- 同一メールアドレスを2つの Terraform リソース(送信元用と宛先用)で管理すると、destroy 時にもう一方の Identity も巻き添えで失効する
- サブドメイン名は後から短縮したくなった(
mail.prd.myapp...→myapp...)が、Hosted Zone の作り直し = NS 委譲のやり直しになる。「このサブドメイン配下をメール以外に使うか」は最初に決めておくべきだった
結果
送信元が DKIM 署名 + SPF アライメント + DMARC の揃ったドメイン Identity になり、迷惑メール判定は解消しました。DMARC の p=none → p=quarantine への引き上げは、レポートを確認しながら別途進める予定です。
まとめ
- SES のメールアドレス Identity(DKIM なし・SPF 不一致)は迷惑メール判定されやすい。送信専用サブドメインの Domain Identity + Easy DKIM + Custom MAIL FROM + DMARC で解消
- 親ゾーンが別管理のときは「リソース作成」と「検証 + 切り替え」を2段階の PR に分割する
- stg の NS 委譲を prd ゾーン内で行うと、全社 DNS への依存を最小化できる
- SES の IAM 認可は宛先の検証済み Identity にも掛かる
参考になれば幸いです。
