はじめに
この記事は執筆時点(2026年5月時点)の情報になります。最新情報は各種サイトでご確認ください。
アプリケーションのログインを管理するAmazon Cognitoからログインに関するメール(例:TOTP(Time-based One Time Password)のお知らせ等)を送付しようとした際に、ユーザーがicloud.comを利用しているときに限ってメールがバウンス(迷惑メールフォルダにすら届かない)する事象に遭遇しました。
gmail等の他の著名なメールプロバイダー宛ての配信は問題なくできていました。非常にニッチな事象に遭遇したため、ネットに転がっている参考記事も少なく、解決まで苦労したので、ここに記録を残します。
※メールサンプル画像は生成AIツールで生成しました。
結論
Amazon Cognitoからの発信するユーザーログイン情報関連のメールが「広告一斉メール配信」と判断された可能性がある。
対策としてReply-toの設定を行うことで、icloudメールへの到達率100%を達成。
(ただし、iCloud側の判定ロジックは公開されていないため、原因を断定することは不可。
すべての環境で同じ結果になることを保証するものではありません。)
対象読者
- フルスタックエンジニアとしてアプリケーション+クラウドにかかわる方
- アプリケーション開発において、初めてメール配信機能を開発される方
- iCloudメールへの配信達成率に課題を感じている方
- Amazon Cognito, Amazon SESに興味がある方
前置き_Amazon Cognitoのメール配信機能
Amazon Cognitoのユーザープールでは、ユーザー登録時のパスワード配信、パスワードリセット時の確認コード,、MFAコード、メールOTP、などをメールで送信できます。
メール配信には大きく分けて、以下の2つの選択肢があります。
- 選択肢1:Amazon SESと連携して、独自ドメインのメールアドレスから送信する
- 選択肢2:Cognitoのデフォルトメール機能
| 評価項目 | 選択肢1: Amazon SESと連携して、独自ドメインのメールアドレスから送信する | 選択肢2: Cognitoのデフォルトメール機能を使う |
|---|---|---|
| 主な用途 | 本番環境や継続的にユーザーへメールを送る用途に向いている | 検証環境や小規模な利用に向いている |
| 送信元メールアドレス | 独自ドメインのメールアドレスを送信元にできる | Cognito側で用意された送信元を利用する。デフォルトではno-reply@verificationemail.comから送信される |
| レート制限 | SESの送信クォータや送信レートに従う。必要に応じてクォータ引き上げ申請も可能 | デフォルトメール機能では、1 AWSアカウントあたり50通/24hの制限がある。一般的な本番環境では不足しやすい |
| メール配信の可観測性 | SESのイベント送信、CloudWatch、SNSなどと組み合わせて、バウンス・苦情・配信状況を追跡しやすい | バウンスや苦情などの詳細な配信イベントを追いづらい |
| DNS設定 | SPF、DKIM、DMARCなどのDNS設定が必要 | 基本的には不要で、すぐに使い始めやすい |
| 到達性の調整 | 送信ドメイン、DKIM、MAIL FROM、Reply-toなどを自分たちで調整できる | 送信元や認証まわりを細かく制御しづらい |
| 運用コスト | DNS、SES、バウンス管理などの運用が必要 | 低い。まず動かすまでが簡単 |
特にレート制限部分の影響が大きく、デフォルトメール機能の50通/24hに耐えられる本番環境はめったにないと思います。本番環境でのメール配信はSESを使う前提で検討されるのが良いと思います。
参考: Amazon Cognito ユーザープールの E メール設定
前置き_Amazon SESとDNSレコード
Amazon SESで独自ドメインからメールを送信する場合、最初はsandbox環境として割り当てられ、厳しいレート制限が課されます。必要な手続きを済ませたうえでAWSサポートに申請を行うことで、数千、数万件以上のメール配信が可能になります。
この手続きとしてドメインの所有者確認(IDの設定)が求められ、SESから指定されるDNSレコードをドメイン管理画面(お名前ドットコムやAmazon Route53等)に登録する必要があります。
メールの受信側は、そのメールが正当な送信元から送られているかを複数の観点で確認します。代表的なものが以下です。
- SPF: そのドメインのメールを送信してよいサーバーかを確認する仕組み
- 例:
hogehoge.example.com TXT "v=spf1 include:amazonses.com ~all"
- 例:
- DKIM: メールに電子署名を付け、送信ドメインの正当性や改ざん有無を確認する仕組み
- 例:
hugahuga001._domainkey.hogehoge.example.com CNAME hugahuga001.dkim.amazonses.com
- 例:
- DMARC: SPFやDKIMの結果をもとに、受信側がどう扱うべきかを送信ドメイン側が宣言する仕組み
- 例:
_dmarc.hogehoge.example.com TXT "v=DMARC1; p=none; rua=mailto:dmarc-report@hogehoge.example.com"
- 例:
SESでドメインIDを作成すると、DKIM用のCNAMEレコードなど、DNSに追加すべきレコードが表示されます。Route 53を使っている場合は比較的簡単ですが、外部DNSを使っている場合は、レコード名の末尾のドメイン重複や、TXTレコードの値のクォートなどでつまずきやすいです。
今回の調査では、まず「iCloudだけに届かない」という現象から、送信ドメイン自体の信頼度に問題があるのではないかと考え、DNS認証まわりに不備があるのではないかと疑いました。
参考: Amazon SES の DMARC 認証プロトコルへの準拠
メールが送れない事象のトラブルシューティング過程
エラーコードとAppleのローカルポリシーについて
まず確認したのは、バウンスメールに含まれていたエラー内容です。
今回確認できたエラーは、概ね以下のような内容でした。
554 5.7.1 [HM08] Message rejected due to local policy
受信側であるApple/iCloud側のローカルポリシーにより拒否された、という意味になります。
Appleはどのような観点でメールフィルターを行っているか、アルゴリズムを公表していませんが、配信率を向上させるためのベストプラクティスを発信しています。
下記公式サイトの中で、SPF、DKIM、DMARC、送信ドメインやIPの一貫性、バウンス管理などの具体的なアクションが記載されています。
参考: Postmaster information for iCloud Mail
利用ドメインのSPF, DKIM, DMARCの確認方法
次に、送信ドメインの認証状態を確認しました。
確認した観点は以下です。
- SPFレコードが存在するか
- DKIMが有効になっているか
- SESのドメインIDがVerifiedになっているか
- DMARCレコードが存在するか
- DMARCのポリシーが不適切になっていないか?
- Fromドメインと認証ドメインの整合性が取れているか
MXToolBox
まずはMXToolBoxで、SPF、DKIM、DMARCの状態を確認しました。
MXToolBoxでは、ドメインを入力するだけでDNSレコードの存在や構文エラーを確認できます。
結果のスクリーンショットは掲載しませんが、今回のケースでは、SPFとDMARCのレコード自体は存在しており、明らかな構文エラーも見つかりませんでした。
MX ToolBox
mail-tester
次にmail-testerを使って、実際に送信したメールのスコアを確認しました。
mail-testerでは、指定された一時メールアドレスにテストメールを送信すると、SPF、DKIM、DMARC、本文、ヘッダー、ブラックリストなどをまとめて診断してくれます。
テスト結果のスコアに関しても8.X点/10点満点と比較的高スコアであり、SPF、DKIM、DMARCはいずれも大きな問題なしという結果でした。
つまり、基本的なメール認証の設定ミスが原因ではなさそうでした。
AppleのiCloudチームへの確認
仮説が外れて、対策案の手持ちが0になってしまいました。
なんとか前に進めなければいけないので、公開されているAppleのPostmasterチーム連絡先を確認し、メールでの相談を行いました(英語です)。
Appleへ問い合わせる場合は、以下のような情報をまとめておくと話が早いです。
- メールタイトル
- 「Follow-up Request:~~~」をつける(少しでも見ていただけるように工夫しました。)
- 本文
- 企業名
- 送信ドメイン/送信元メールアドレス
- 送信元IPアドレス
- バウンスした日時等のエラー情報
- そのほかの補足情報
超意訳ですが、こんな回答をいただけました。
「Campaignメールに該当している可能性があるので、ベストプラクティスに従って見直してみて」
参考: (再掲)Postmaster information for iCloud Mail
得られた仮説とマネジメントコンソールからのテストメール配信
この状況から、以下の仮説を立てました。
- メール認証そのものよりも、メールヘッダーや送信者情報の見え方が影響している
- 送信元ドメインやメール本文自体が広告っぽい
- Fromは設定済みだが、返信先や問い合わせ先の情報が薄く、トランザクションメールとしての信頼性が低く見えている
SESのマネジメントコンソール上ではテストメールの配信を行うことでき、トライアンドエラーを繰り返したところ、下記知見が得られました。
| 到達率改善効果 | 実施した内容 | 備考 |
|---|---|---|
| ◎ | メールの返信先設定 (Reply-To) を追加する |
- |
| △ |
List-Unsubscribe など配信停止系ヘッダーを設定した MIME メールを配信する & HTML + CSS を最小限にして、ほぼテキストメール状態で配信する |
届く場合と届かない場合があり、不安定 |
実際の対処策
最終的に効果があったのは、Cognitoのメール設定に返信先Eメールアドレス(Reply-to)を設定することでした。
Cognitoのユーザープールでは、メール設定の中でFromアドレスだけでなく、Reply-toアドレスも設定できます。CloudFormationやCDKで管理している場合は、ユーザープールのメール設定にReplyToEmailAddressを追加します。
CloudFormationの場合のイメージです。
EmailConfiguration:
EmailSendingAccount: DEVELOPER
From: "Example App <no-reply@example.com>"
SourceArn: arn:aws:ses:ap-northeast-1:123456789012:identity/example.com
ReplyToEmailAddress: support@example.com
設定後、iCloudメールアドレス宛てにCognitoの認証コードメールを複数回送信し、バウンスが発生しないことを確認しました。
今回の環境では、この対応によりiCloudメールへの到達率が100%になりました。
なぜReply-toが効いたのかを断定することはできません。iCloud側のフィルタリングロジックは公開されていないためです。ただ、受信側から見ると、返信先が明示されているメールは、完全な一方通行の一斉配信メールよりも、送信者の実体が分かりやすいメールとして評価された可能性があります。
その他の検討した対策_CustomEmailSenderトリガーによるLambda上からのメール配信
広告メールとして判断されるため、iCloudのベストプラクティスに従い、「広告配信を停止するリンク」を含ませることもテスト上では効果が大きかったです。
Cognito標準のメール送信では、List-Unsubscribeなどの任意ヘッダーを細かく制御できません。
そのため、任意ヘッダーを明示的に付与したい場合は、CustomEmailSenderトリガーでLambdaからSESを呼び出し、Raw/MIMEメールとして送信する構成が選択肢になります。
この方式にすると、メールヘッダーや本文などをより柔軟に設定することができます。
一方で、CustomEmailSenderを利用する場合は、Cognitoがコードを暗号化するための対称暗号化KMSキー(カスタマーマネージドキー)を用意し、Lambda側でAWS Encryption SDKを使って復号する必要があります。
- センシティブ情報の処理をLambda内で扱わなければいけないこと
- 「KMSキーのアクセス管理」等のカギ管理が運用で発生すること
上記をリスクと考え、なるべくならこの解決プランを採用しない方針にしたい思いがありました。
import base64
import html
import os
import aws_encryption_sdk
import boto3
from aws_encryption_sdk.identifiers import CommitmentPolicy
ses = boto3.client("sesv2")
KEY_ARN = os.environ["KEY_ARN"]
FROM_EMAIL = os.environ["FROM_EMAIL"]
REPLY_TO_EMAIL = os.environ["REPLY_TO_EMAIL"]
def decrypt_code(encrypted_code: str) -> str:
client = aws_encryption_sdk.EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT
)
key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(
key_ids=[KEY_ARN]
)
plaintext, _ = client.decrypt(
source=base64.b64decode(encrypted_code),
key_provider=key_provider,
)
return plaintext.decode("utf-8")
def lambda_handler(event, context):
"""
eventから受け取ったCognitoの情報をKMSを用いて復号処理を行う
"""
encrypted_code = event["request"]["code"]
code = decrypt_code(encrypted_code)
user_attributes = event["request"].get("userAttributes", {})
to_email = user_attributes["email"]
trigger_source = event.get("triggerSource", "")
subject = "メールタイトル"
escaped_code = html.escape(code)
text_body = f"""メール本文"""
ses.send_email(
FromEmailAddress=FROM_EMAIL,
Destination={
"ToAddresses": [to_email],
},
ReplyToAddresses=[REPLY_TO_EMAIL],
Content={},
)
return event
最後に
今回の事象は、SPF、DKIM、DMARCが大きく間違っていなくても、iCloudだけでバウンスするという分かりにくいものでした。同じようにメールバウンスで悩んでいる方の参考になれば幸いです。
Appleのフィルターは非公開であるため正確な理由がわかりませんが、もしCognito + SESでiCloud宛てのメールだけが届かない場合は、DNS認証(SPF,DKIM.,DMARC)の確認に加えて、Reply-toの設定も試してみてください。



