TL;DR
Route53で管理している独自ドメインを使って、AI Agent用にAmazon SESでのメール送受信環境をAWSコンソールで組みました。受信は support@<ドメイン名> 宛のメールをS3バケットに保存する仕組みで、送信用にはドメインをDKIM付きで検証し、専用のMAIL FROMドメインも設定しています。受信ルールを作成しようとすると Could not write to bucket: <S3バケット名> というエラーに遭遇します。調べてみると、S3アクションにIAMロールを指定した場合はバケットポリシーではなくそのロールの権限だけが使われる、という仕様が原因でした。対象バケットに限定した最小権限のIAMポリシーを作成して既存のIAMロールにアタッチしたところ、無事に受信できるようになりました。
SESで受信してS3に保存するまでの構成
全体像は、Route53で管理するドメイン → SESでのドメイン認証・受信ルール → IAMロール → S3への保存という流れです。
対象ドメインのHosted Zoneには、apexの MX レコード(10 inbound-smtp.ap-northeast-1.amazonaws.com、SESの受信用)と、SESドメイン検証用のDKIM CNAME を3件、そしてカスタムMAIL FROMドメイン(mail.<ドメイン名>)用の MX と TXT(SPF)を設定しています。apexの MX レコードはマニュアルで設定が必要でしたが検証用ドメインはRoute53のケースは自動で追加されます。リージョンは ap-northeast-1 を選び、受信ルールセット <受信ルールセット名> の配下に受信ルール <受信ルール名> を作成しました。受信者アドレスは support@<ドメイン名>、アクションはS3バケット <S3バケット名> への保存で、スパム/ウイルススキャン(ScanEnabled: true)も有効にし、S3アクションには専用に用意したIAMロール <IAMロール名> のARNを指定しています。ドメインID <ドメイン名> の検証ステータスは、AWS APIで確認したところ Success(DKIM検証済み)でした。
IAMロールとポリシーの実際の設定は、次のようになっています。
// IAMロール <IAMロール名> の信頼ポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ses.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"AWS:SourceAccount": "<AWSアカウントID>",
"AWS:SourceArn": "arn:aws:ses:ap-northeast-1:<AWSアカウントID>:receipt-rule-set/<受信ルールセット名>:receipt-rule/<受信ルール名>"
}
}
}
]
}
AWS:SourceArn は対象の受信ルールを指すARNまで絞り込んであるので、このロールをAssumeできるのはこの受信ルールからのSESリクエストだけになります。ロール自体の実行権限は、あとで作成した <IAMポリシー名> で次のように絞りました。
// IAMポリシー <IAMポリシー名>
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetBucketLocation", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::<S3バケット名>",
"arn:aws:s3:::<S3バケット名>/*"
]
}
]
}
バケット <S3バケット名>(ap-northeast-1)には、SESサービスプリンシパルからの s3:PutObject を許可するバケットポリシーも別途設定していて、こちらも AWS:SourceAccount とSESのARNワイルドカードで絞り込んでいます。
送信の設定
ここまでは受信側の話でしたが、<ドメイン名> は送信用のIDとしても検証済みで、DKIM署名(DkimEnabled: true、検証ステータス Success)が有効になっています。あわせて、送信メールのMAIL FROMアドレスに使うサブドメイン mail.<ドメイン名> も検証済み(Success)で、SPFレコードとセットで設定してあります。サンドボックス環境のため動作確認用に個人のGmailアドレスも受信側のIDとして検証しておき、そこにテスト送信を2件行いました。
送信できるのは検証済みのアドレス宛のみで、送信数の上限も24時間あたり200件、送信レートも1件/秒に制限されています。今後、実際に不特定多数へ送るには、AWSに本番アクセスを申請する必要があります。
Could not write to bucket
受信ルールを作成しようとすると、次のエラーが返ってきました。
InvalidS3ConfigurationException: Could not write to bucket: <S3バケット名>
バケットポリシー側では既に ses.amazonaws.com からの s3:PutObject を許可していたため、その時点では権限は足りているように見えます。正直すぐには原因が分かりませんでした。AWS公式ドキュメント(Amazon SES Developer Guide「Giving permissions to Amazon SES for email receiving」)を読み直すと、原因はこの一文にありました。
If an IAM role ARN is provided, the role (and only the role) is used to access all the given resources (Amazon S3 bucket, AWS KMS customer managed key and Amazon SNS topic).
つまり受信ルールのS3アクションにIAMロールを指定すると、バケットポリシーは評価されなくなり、ロール自身が持つ権限だけがチェックされる、という仕様です。<IAMロール名> の信頼ポリシー(誰がAssumeできるか)は上で見た通り正しく設定していましたが、ロール自体には実行権限(何ができるか)を何も付与していなかったのが原因でした。
対策として、上で載せた <IAMポリシー名>(対象バケットへの s3:PutObject 等に限定)を新規作成し、<IAMロール名> にアタッチしたところ解決しました。
構成図
Route 53からSES、IAMロールを経由してS3に保存されるまでの流れを図にすると、次のようになります。
学び・今後の課題
SESの受信ルールでS3アクションにIAMロールを指定する場合、バケットポリシーではなくロール側の権限がすべてになるので、バケットポリシーだけで運用するか、ロールを指定して対象リソースを絞った最小権限を付与するかのどちらかにするのがよさそうです。S3に保存されたメールをどう後続処理につなげるか、Lambdaトリガー等での自動処理はまだ未着手です。送信側もサンドボックス状態のままなので、AI エージェントから実際にメールを送るにはAWSへの本番アクセス申請が必要になります。
参考リンク
- Giving permissions to Amazon SES for email receiving — バケットポリシー方式とIAMロール方式の使い分け、および「IAMロールを指定した場合はロールのみが使われる」という仕様の一次情報
