最新のアップグレード後、ARN設定が不足しているというプロンプトが表示されたため、AWS SESからのバウンス通知を設定しようとしていました。トピックを設定しARNを追加しましたが、テスト用のバウンスメールが表示されませんでした。ただし、AWSでトピックに届いたことは確認しています。以下は、Codexによる分析と提案されたパッチです(未テスト)。
サマリー
SNS経由で配信されるAmazon SESのバウンス通知は /webhooks/aws によって受け入れられますが、現在のDiscourseでは、SESがSMTP経由で使用されている場合、対応する EmailLog がバウンスとしてマークされません。
Jobs::ProcessSnsNotification は mail.messageId を使ってログを検索します。AWSのドキュメントによると、これはSESによって割り当てられた識別子であり、Email::Sender は EmailLog.message_id にDiscourseの元のRFC Message-ID を保存します。これらの2つの識別子は異なります。
これは、2026年6月のセキュリティ強化コミット 61f12e13aa1b760f81d5ff60f12e3a7e77434b94 によって導入されたリグレッション(機能劣化)であると考えられます。トピックの許可リスト、署名検証、受信者バインディング、重複保護は維持されるべきであり、変更すべきは検索に使用する識別子だけです。
環境
- Discourseコミット:
2239124ce41df4ea23a21686a78342adb5f6b3b5 - Amazon SES SMTP送信
- SESバウンス通知トピックがSNS経由で
/webhooks/awsに配信される設定 aws_sns_topic_arn_allowlistに正確なSNSトピックARNが含まれている- SNSの購読が確認済み
再現手順
- DiscourseをAmazon SES SMTPエンドポイント経由で送信するように設定します。
- 元のヘッダーを含むSESバウンス通知トピックを設定します。
https://<discourse-host>/webhooks/awsをそのトピックに購読させます。- そのトピックARNを
aws_sns_topic_arn_allowlistに追加します。 bounce@simulator.amazonses.com宛にDiscourseメールを送信します。- SESがバウンスを公開し、SNSがHTTPS配信の成功を報告することを確認します。
/admin/email-logs/bouncedを開きます。
実際の結果
Webhookは成功を返し、SNSは配信失敗を報告しませんが、メールはバウンスメールログに表示されず、そのDiscourseのバウンス状態も更新されません。
期待される結果
Discourseは、元のRFC Message-ID とバウンスした受信者を使用して、SESのバウンスを送信された EmailLog と一致させるべきであり、その後バウンス状態とスコアを更新する必要があります。
原因
Discourseは配信前のメッセージIDを保存しています:
email_log.message_id = @message.message_id
現在のSNSジョブはSESによって割り当てられたIDを使用しています:
message_id = message.dig("mail", "messageId")
AWSはこれらのフィールドを区別しています:
mail.messageIdはSESによって割り当てられます。- メール元の
Message-IDは、元のヘッダーが有効な場合、mail.headersとmail.commonHeadersで利用可能です。
現在のリクエスト仕様では、mail.messageId、mail.headers のMessage-ID、mail.commonHeaders.messageId、および EmailLog.message_id に同じフィクスチャ値が与えられているため、実際のSESの動作を再現していません。
この不一致に関する直接のプロジェクト履歴があります: PR #7284 は2019年に、SNSのIDが EmailLog のIDと等しくなかったため、厳密なIDマッチングを削除しました。2026年6月のセキュリティ修正では厳密なマッチングが復活しましたが、SESによって割り当てられたIDが使用されました。
提案される修正
mail.commonHeaders.messageId を優先し、Email::MessageIdService.message_id_clean で正規化し、元のヘッダーが利用できない場合の互換性のために mail.messageId にフォールバックします。既存の (message_id, to_address) 検索、TopicArn許可リスト、SNS署名検証、重複処理を維持します。
添付のパッチはまた、SESによって割り当てられたIDが元のRFC Message-IDと異なるようにリクエストフィクスチャを変更します。
diff --git a/app/jobs/regular/process_sns_notification.rb b/app/jobs/regular/process_sns_notification.rb
index 2785887d..21ebb1a2 100644
--- a/app/jobs/regular/process_sns_notification.rb
+++ b/app/jobs/regular/process_sns_notification.rb
@@ -17,7 +17,13 @@ module Jobs
end
return unless message && message["notificationType"] == "Bounce"
- return unless message_id = message.dig("mail", "messageId").presence
+ message_id =
+ message.dig("mail", "commonHeaders", "messageId").presence ||
+ message.dig("mail", "messageId").presence
+ return unless message_id
+
+ message_id = Email::MessageIdService.message_id_clean(message_id.strip)
+
return unless bounce_type = message.dig("bounce", "bounceType").presence
return if !Email::Sns.allowed_topic_arn?(json["TopicArn"])
diff --git a/spec/requests/webhooks_controller_spec.rb b/spec/requests/webhooks_controller_spec.rb
index eb7f5b44..8c77b337 100644
--- a/spec/requests/webhooks_controller_spec.rb
+++ b/spec/requests/webhooks_controller_spec.rb
@@ -794,6 +794,7 @@ RSpec.describe WebhooksController do
let(:topic_arn) { "arn:aws:sns:us-east-1:123456789012:discourse-bounces" }
let(:other_topic_arn) { "arn:aws:sns:us-east-1:999999999999:attacker-topic" }
let(:bounce_status) { "5.1.1" }
+ let(:ses_message_id) { "000001378603177f-7a5433e7-8edb-42ae-af10-f0181f34d6ee-000000" }
let(:payload) do
{
"Type" => "Notification",
@@ -823,7 +824,7 @@ RSpec.describe WebhooksController do
"sourceIp" => "127.0.3.0",
"sendingAccountId" => "123456789012",
"callerIdentity" => "IAM_user_or_role_name",
- "messageId" => message_id,
+ "messageId" => ses_message_id,
"destination" => [email, "jane@example.com", "mary@example.com", "richard@example.com"],
"headersTruncated" => false,
"headers" => [
@@ -833,7 +834,7 @@ RSpec.describe WebhooksController do
"value" =>
"\"Test\" <#{email}>, \"Jane Doe\" <jane@example.com>, \"Mary Doe\" <mary@example.com>, \"Richard Doe\" <richard@example.com>",
},
- { "name" => "Message-ID", "value" => message_id },
+ { "name" => "Message-ID", "value" => "<#{message_id}>" },
{ "name" => "Subject", "value" => "Hello" },
{ "name" => "Content-Type", "value" => "text/plain; charset=\"UTF-8\"" },
{ "name" => "Content-Transfer-Encoding", "value" => "base64" },
@@ -845,7 +846,7 @@ RSpec.describe WebhooksController do
"to" => [
"\"Test\" <#{email}>, Jane Doe <jane@example.com>, Mary Doe <mary@example.com>, Richard Doe <richard@example.com>",
],
- "messageId" => message_id,
+ "messageId" => "<#{message_id}>",
"subject" => "Hello",
},
},
@@ -870,7 +871,7 @@ RSpec.describe WebhooksController do
SiteSetting.aws_sns_topic_arn_allowlist = topic_arn
end
- it "hard bounces" do
+ it "hard bounces using the original message ID" do
user = Fabricate(:user, email: email)
email_log = Fabricate(:email_log, user: user, message_id: message_id, to_address: email)
@@ -883,7 +884,7 @@ RSpec.describe WebhooksController do
expect(email_log.user.user_stat.bounce_score).to eq(SiteSetting.hard_bounce_score)
end
- it "does not bounce an email log with a different SES message id" do
+ it "does not bounce an email log with a different original message ID" do
user = Fabricate(:user, email: email)
email_log =
Fabricate(:email_log, user: user, message_id: "other-message-id", to_address: email)
参考
- AWS通知フィールド定義: Amazon SNS notification contents for Amazon SES - Amazon Simple Email Service
- 元のDiscourse修正、PR #7284: FIX: Detect SNS notifications for SES correctly - Pull Request #7284 - discourse/discourse - GitHub
- セキュリティ強化コミット: SECURITY: Prevent any signed AWS SNS TopicARN from being accepted via… · discourse/discourse@61f12e1 · GitHub
- 既存のMetaレポート: Bounced e-mails from Amazon SES/SNS not working