Stavo cercando di configurare le notifiche di rimbalzo da AWS SES dopo aver ricevuto un avviso sulla mancata configurazione dell’ARN al termine dell’ultimo aggiornamento. Ho configurato il topic e aggiunto l’ARN, ma una email di test di rimbalzo non è mai arrivata. Tuttavia, ho confermato che è stata ricevuta dal topic su AWS. Di seguito è riportata un’analisi e una patch proposta da Codex (non testata).
Riepilogo
Le notifiche di rimbalzo di Amazon SES consegnate tramite SNS vengono accettate da /webhooks/aws, ma l’attuale versione di Discourse non segna il corrispondente EmailLog come rimbalzato quando SES viene utilizzato tramite SMTP.
Jobs::ProcessSnsNotification cerca il log utilizzando mail.messageId. AWS documenta che questo è un identificatore assegnato da SES, mentre Email::Sender archivia l’originale RFC Message-ID di Discourse in EmailLog.message_id. I due identificatori sono diversi.
Questo sembra essere un regresso introdotto dal rafforzamento della sicurezza di giugno 2026 nel commit 61f12e13aa1b760f81d5ff60f12e3a7e77434b94. La whitelist dei topic, la verifica della firma, il vincolo del destinatario e la protezione dai duplicati dovrebbero rimanere intatti; è necessario modificare solo l’identificatore utilizzato per la ricerca.
Ambiente
- Commit di Discourse:
2239124ce41df4ea23a21686a78342adb5f6b3b5 - Invio tramite SMTP di Amazon SES
- Topic di notifica dei rimbalzi SES consegnato tramite SNS a
/webhooks/aws aws_sns_topic_arn_allowlistcontiene l’ARN esatto del topic SNS- L’abbonamento SNS è confermato
Passaggi per riprodurre
- Configura Discourse per inviare tramite l’endpoint SMTP di Amazon SES.
- Configura un topic di notifica dei rimbalzi SES, includendo gli header originali.
- Abbona
https://<discourse-host>/webhooks/awsal topic. - Aggiungi quell’ARN del topic a
aws_sns_topic_arn_allowlist. - Invia un’email di Discourse a
bounce@simulator.amazonses.com. - Conferma che SES pubblica il rimbalzo e che SNS riporta una consegna HTTPS riuscita.
- Apri
/admin/email-logs/bounced.
Risultato effettivo
Il webhook restituisce successo e SNS non riporta alcuna consegna fallita, ma l’email è assente dal registro delle email rimbalzate e il suo stato di rimbalzo in Discourse non viene aggiornato.
Risultato atteso
Discourse dovrebbe abbinare il rimbalzo SES all’EmailLog inviato utilizzando l’originale RFC Message-ID e il destinatario rimbalzato, quindi aggiornare lo stato di rimbalzo e il punteggio.
Causa
Discourse archivia l’ID messaggio pre-consegna:
email_log.message_id = @message.message_id
Il job SNS attualmente utilizza l’ID assegnato da SES:
message_id = message.dig("mail", "messageId")
AWS distingue questi campi:
mail.messageIdè assegnato da SES.- L’originale
Message-IDdell’email è disponibile inmail.headersemail.commonHeadersquando gli header originali sono abilitati.
Lo spec di richiesta attuale assegna a mail.messageId, Message-ID in mail.headers, mail.commonHeaders.messageId e EmailLog.message_id lo stesso valore di fixture, quindi non riproduce il comportamento reale di SES.
C’è una storia diretta del progetto per questa discrepanza: la PR #7284 ha rimosso l’abbinamento rigoroso degli ID nel 2019 perché l’ID SNS non era uguale all’ID in EmailLog. La modifica di sicurezza di giugno 2026 ha ripristinato l’abbinamento rigoroso ma ha utilizzato l’ID assegnato da SES.
Soluzione proposta
Preferire mail.commonHeaders.messageId, normalizzarlo con Email::MessageIdService.message_id_clean e fare affidamento su mail.messageId per compatibilità quando gli header originali non sono disponibili. Mantenere la ricerca esistente (message_id, to_address), la whitelist TopicArn, la verifica della firma SNS e la gestione dei duplicati.
La patch allegata modifica anche la fixture di richiesta in modo che l’ID assegnato da SES differisca dall’originale 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)
Riferimenti
- Definizioni dei campi di notifica AWS: Amazon SNS notification contents for Amazon SES - Amazon Simple Email Service
- Modifica originale di Discourse, PR #7284: FIX: Detect SNS notifications for SES correctly - Pull Request #7284 - discourse/discourse - GitHub
- Commit di rafforzamento della sicurezza: SECURITY: Prevent any signed AWS SNS TopicARN from being accepted via… · discourse/discourse@61f12e1 · GitHub
- Report Meta esistente: Bounced e-mails from Amazon SES/SNS not working