Core verfügt über einen funktionsfähigen Mandrill-Bounce-Endpunkt – POST /webhooks/mandrill in WebhooksController – der hard_bounce- und soft_bounce-Ereignisse verarbeitet, hard_bounce_score / soft_bounce_score anwendet und Adressen deaktiviert, die den bounce_score_threshold überschreiten. Die Zuordnung erfolgt über den message_id-Header in X-MC-Metadata, den Email::Sender hinzfügt, wenn DISCOURSE_SMTP_ADDRESS exakt smtp.mandrillapp.com ist.
Ich habe versucht, dies einzurichten, bin aber auf einige Probleme gestoßen, die ich teilen wollte, falls es anderen hilft. Ich weiß, dass es sich um einen Nischenfall handelt, da Mandrill seit über einem Jahrzehnt keinen „reinen SMTP“-Dienst mehr anbietet und den SMTP-Dienst erst jetzt als Add-on für das Mailchimp-Produkt für transaktionale E-Mails bereitstellt. Dennoch hatte ein Kunde von mir genau dieses Setup, und so sind wir hier!
1. Es ist nirgendwo dokumentiert, wo ich es finden kann. Das VERP-Thema erwähnt es nicht, was nachvollziehbar ist, da VERP nicht so funktioniert wie Mandrill: Die eigenen Doku von Mailchimp besagen, dass Bounces selbst verarbeitet werden, selbst wenn eine benutzerdefinierte Return-Path-Domain konfiguriert ist, sodass Bounces pro Nachricht nie einen selbst gehosteten Mail-Empfänger erreichen. Die Anbieter-Webhooks sind der einzige Weg, und es gibt keinen offensichtlichen Hinweis darauf für jemanden, der beim VERP-Thema startet.
2. Der Webhook kann tatsächlich nicht erstellt werden. WebhooksController#mandrill gibt 406 zurück, wann immer mandrill_authentication_key leer ist. Dieses Verhalten ist absichtlich – es ist die Lösung für CVE-2026-26077 (Discourse 2025.12.2 / 2026.1.1 / 2026.2.0), die das unautorisierte Fälschen von Bounce-Payloads über die Endpunkte von SendGrid, Mailjet, Mandrill, Postmark, SparkPost und Mailpace unterband. Es geht also nicht darum, dies zu lockern; vielmehr hat die Lösung Mandrill ohne Möglichkeit zur Initialisierung zurückgelassen.
Mandrill validiert einen neuen Webhook mit einem POST (nicht dem HEAD, für das mandrill_head geschrieben wurde), sieht die 406 und lehnt das Speichern des Webhooks ab – so wird der benötigte Schlüssel nie sichtbar. Der API-Pfad schlägt identisch fehl:
{"status":"error","code":-98,"name":"ValidationError","message":"Unable to validate webhook URL"}
Ursprungsprotokoll für einen Versuch über die Benutzeroberfläche:
"POST /webhooks/mandrill HTTP/2.0" "Mandrill-Webhook/1.0" 406
Die anderen Anbieter haben kontostufige Tokens, sodass ein Administrator ein beliebiges Geheimnis vor der Erstellung des Webhooks festlegen kann. Der Mandrill-Schlüssel wird pro Webhook ausgestellt, erst nachdem der Webhook gespeichert wurde – es ist also ein Teufelskreis.
Ich bin mir ziemlich sicher, dass der Ablauf behoben werden könnte, vielleicht indem auf die erste Validierung (die keine mandrill_events-Payload enthält) mit 200 geantwortet wird, um die Erstellung in Mandrill zu ermöglichen, dann kann das Geheimnis manuell in Discourse kopiert werden, und wir sind durch. Ich bin bereit, einen PR zu öffnen, wenn das akzeptabel wäre.
3. Workaround, falls es jemanden heute braucht. Erstelle den Webhook gegen eine URL, die bereits 200 zurückgibt, mit keinen Triggern (Bounce-Payloads enthalten Empfängeradressen, daher nie einen ausgelösten Webhook auf einen Platzhalter zeigen), installiere dann den Schlüssel in Discourse und verschiebe die Webhook-URL, sobald dies erledigt ist:
# 1. gegen einen Platzhalter erstellen, keine Ereignisse
curl -sS -X POST https://mandrillapp.com/api/1.0/webhooks/add.json \
-H 'Content-Type: application/json' \
-d '{"key":"<api-key>","url":"https://httpbin.org/status/200","description":"Discourse bounce handling","events":[]}'
# 2. den zurückgegebenen auth_key in die mandrill_authentication_key-Website-Einstellung eintragen
# 3. auf das Forum umleiten und Trigger aktivieren – dies besteht nun die Validierung
curl -sS -X POST https://mandrillapp.com/api/1.0/webhooks/update.json \
-H 'Content-Type: application/json' \
-d '{"key":"<api-key>","id":<id>,"url":"https://<forum>/webhooks/mandrill","description":"Discourse bounce handling","events":["hard_bounce","soft_bounce"]}'
Getestet auf 2026.9.0-latest.