Ich habe versucht, Bounce-Benachrichtigungen von AWS SES einzurichten, nachdem nach dem letzten Upgrade eine Meldung über eine fehlende ARN-Konfiguration für diese Funktion angezeigt wurde. Ich habe das Topic konfiguriert und die ARN hinzugefügt, aber eine Test-Bounce-E-Mail kam nie an. Ich habe jedoch bestätigt, dass sie das Topic auf AWS erreicht hat. Nachfolgend findet sich eine Analyse und ein vorgeschlagener Patch von Codex (ungetestet).
Zusammenfassung
Amazon-SES-Bounce-Benachrichtigungen, die über SNS zugestellt werden, werden von /webhooks/aws akzeptiert, aber das aktuelle Discourse markiert die entsprechende EmailLog nicht als „bounced“, wenn SES über SMTP verwendet wird.
Jobs::ProcessSnsNotification sucht den Eintrag anhand von mail.messageId ab. AWS dokumentiert, dass dies eine von SES zugewiesene Kennung ist, während Email::Sender die ursprüngliche RFC-Message-ID von Discourse in EmailLog.message_id speichert. Die beiden Kennungen unterscheiden sich.
Dies scheint ein Regressionsfehler zu sein, der durch die Sicherheitsverstärkung im Juni 2026 im Commit 61f12e13aa1b760f81d5ff60f12e3a7e77434b94 eingeführt wurde. Die Topic-Allowlist, die Signaturverifizierung, die Empfängerbindung und der Schutz vor Duplikaten sollten unverändert bleiben; nur die für die Suche verwendete Kennung muss geändert werden.
Umgebung
- Discourse-Commit:
2239124ce41df4ea23a21686a78342adb5f6b3b5 - Amazon-SES-SMTP-Versand
- SES-Bounce-Benachrichtigungstopic, das über SNS an
/webhooks/awszugestellt wird aws_sns_topic_arn_allowlistenthält die genaue SNS-Topic-ARN- SNS-Abonnementsbestätigung liegt vor
Schritte zur Reproduktion
- Konfiguriere Discourse so, dass es über den Amazon-SES-SMTP-Endpunkt sendet.
- Konfiguriere ein SES-Bounce-Benachrichtigungstopic, einschließlich der Original-Header.
- Abonniere
https://<discourse-host>/webhooks/awsfür das Topic. - Füge die ARN dieses Topics zu
aws_sns_topic_arn_allowlisthinzu. - Sende eine Discourse-E-Mail an
bounce@simulator.amazonses.com. - Stelle sicher, dass SES den Bounce veröffentlicht und SNS eine erfolgreiche HTTPS-Zustellung meldet.
- Öffne
/admin/email-logs/bounced.
Tatsächliches Ergebnis
Der Webhook gibt Erfolg zurück und SNS meldet keine fehlgeschlagene Zustellung, aber die E-Mail fehlt im Protokoll der abgeprallten E-Mails und ihr Discourse-Bounce-Status wird nicht aktualisiert.
Erwartetes Ergebnis
Discourse sollte den SES-Bounce mit dem gesendeten EmailLog anhand der ursprünglichen RFC-Message-ID und des abgeprallten Empfängers abgleichen und anschließend den Bounce-Status und die Punktzahl aktualisieren.
Ursache
Discourse speichert die vor der Zustellung generierte Message-ID:
email_log.message_id = @message.message_id
Der SNS-Job verwendet derzeit die von SES zugewiesene ID:
message_id = message.dig("mail", "messageId")
AWS unterscheidet diese Felder:
mail.messageIdwird von SES zugewiesen.- Die
Message-IDder ursprünglichen E-Mail ist inmail.headersundmail.commonHeadersverfügbar, wenn Original-Header aktiviert sind.
Die aktuelle Request-Spezifikation weist mail.messageId, die Message-ID in mail.headers, mail.commonHeaders.messageId und EmailLog.message_id denselben Fixture-Wert zu, sodass sie das tatsächliche Verhalten von SES nicht reproduziert.
Es gibt eine direkte Projekthistorie für diese Diskrepanz: PR #7284 entfernte 2019 das strikte ID-Matching, weil die SNS-ID nicht der ID in EmailLog entsprach. Die Sicherheitskorrektur im Juni 2026 stellte das strikte Matching wieder her, verwendete aber die von SES zugewiesene ID.
Vorschlag zur Behebung
Verwende bevorzugt mail.commonHeaders.messageId, normalisiere sie mit Email::MessageIdService.message_id_clean und weiche auf mail.messageId aus, wenn Original-Header nicht verfügbar sind (aus Kompatibilitätsgründen). Behalte die bestehende (message_id, to_address)-Suche, die TopicArn-Allowlist, die SNS-Signaturverifizierung und die Duplikatbehandlung bei.
Der angehängte Patch ändert auch das Request-Fixture, sodass sich die von SES zugewiesene ID von der ursprünglichen RFC-Message-ID unterscheidet.
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)
Referenzen
- AWS-Benachrichtigungsfelddefinitionen: Amazon SNS notification contents for Amazon SES - Amazon Simple Email Service
- Ursprüngliche Discourse-Korrektur, PR #7284: FIX: Detect SNS notifications for SES correctly - Pull Request #7284 - discourse/discourse - GitHub
- Commit zur Sicherheitsverstärkung: SECURITY: Prevent any signed AWS SNS TopicARN from being accepted via… · discourse/discourse@61f12e1 · GitHub
- Vorhandener Meta-Bericht: Bounced e-mails from Amazon SES/SNS not working