
Ein Newsletter-Formular im Footer einer Astro-Site, Brevo als Versanddienstleister, Double-Opt-In nach DSGVO-Standard. Der API-Endpoint POST /v3/contacts/doubleOptinConfirmation antwortet mit HTTP 204 — alles in Ordnung. Die Bestätigungsmail kommt beim Empfänger an, sauberer HTML-Body, prominenter Button „Anmeldung bestätigen”. Ein Klick auf den Button.
Nichts passiert.
Der Browser bleibt auf der Mail-Anwendung stehen. Im Quelltext steht: <a href="" target="_blank">…</a> — der href ist leer. Das Verwirrende: der Plaintext-Teil derselben Mail enthält einen funktionierenden Link auf sibcontacts.com/confirm/.... Empfänger mit Plaintext-Clients (selten, aber existent) würden den Bug nie bemerken.
Hier ist die Diagnose, die uns gestern in einem B2B-Mandat zwei Stunden gekostet hat — und die Lösung, die in Brevo’s offizieller API-Reference nicht steht.
Hypothese 1 — Falscher Template-Typ
Brevo unterscheidet intern zwischen „Regular Templates” (Newsletter, Transaktional, Automation) und „DOI Templates”. Letztere lassen sich nur über den Forms-Wizard im Bereich Kontakte → Anmeldeformulare anlegen — nicht über Campaigns → Templates. Wer das nicht weiß, legt ein Regular Template an, gibt die Template-ID an die API und bekommt eine harte Fehlermeldung: {"code":"invalid_parameter","message":"An active DOI template does not exist"}.
Auch über die API ist der DOI-Typ nicht setzbar. POST /v3/smtp/templates und PUT /v3/smtp/templates/{id} akzeptieren ein Feld doiTemplate: true ohne Fehler (HTTP 201/204), ignorieren es aber stillschweigend. Das Template bleibt Regular. Hypothese verworfen, sobald das Template über den Forms-Wizard korrekt als DOI angelegt war — der Endpoint nahm es an, die Mail wurde versendet, der Link blieb trotzdem leer.
Hypothese 2 — HTML-Sanitizer entfernt ungültige hrefs
Naheliegende Vermutung: Brevo’s Sanitizer prüft jeden <a href="…"> auf URL-Gültigkeit, sieht {{ params.DOUBLE_OPT_IN }} als ungültige URL und ersetzt sie durch einen leeren String — bevor die Template-Engine den Platzhalter überhaupt verarbeiten kann. Klassische Pipeline-Reihenfolge-Falle.
Widerlegt durch Vergleich. Andere <a>-Tags im selben Template (Logo-Link, Footer-Links) wurden korrekt durch Brevo’s Click-Tracker gewrappt (gjiicic.r.bh.d.sendibt3.com/tr/cl/...). Der Sanitizer arbeitet also — er greift nur beim DOI-Button nicht.
Hypothese 3 — Brevo’s Visual-Editor verändert das HTML
Brevo nutzt Froala als WYSIWYG-Editor. Beim Speichern setzt Froala in jedes Element fr-original-style- und fr-original-class-Metadaten. Vermutung: er erwartet auch ein fr-original-href und ersetzt fehlende Werte durch leer.
Widerlegt durch Test. HTML komplett im Brevo-UI-Code-Editor eingegeben (nicht über den Visual-Editor), Brevo persistierte es ohne fr-*-Attribute, das Problem trat weiter auf. Froala war es nicht.
Hypothese 4 — Variablen-Name in der Template-Sprache
Letzter Verdacht: die Template-Engine kennt den Platzhalter {{ params.DOUBLE_OPT_IN }} nicht. Ein Test-Template mit sechs Platzhalter-Varianten parallel — params.DOUBLE_OPT_IN, DOUBLE_OPT_IN, doubleoptinlink, OPT_IN_URL, contact.DOUBLE_OPT_IN, params.OPT_IN_URL — hätte den Treffer in der zugestellten Mail offenbart. Bevor wir das fahren mussten, brachte die zweite Doku-Quelle die Antwort.
Brevo’s deutschsprachiges Hilfe-Center (nicht die API-Reference) nennt eine andere Variable: {{ doubleoptin }} — kleingeschrieben, ohne params.-Präfix. Genau die, die in der API-Reference nicht erwähnt wird.

Die Lösung
<!-- Falsch (laut API-Reference) -->
<a href="{{ params.DOUBLE_OPT_IN }}">Anmeldung bestätigen</a>
<!-- Richtig (laut Hilfe-Center) -->
<a href="{{ doubleoptin }}">Anmeldung bestätigen</a>
Template-Tag optin setzen, Test-DOI fahren, Link funktioniert. Sauber.
Der API-Call selbst bleibt unverändert:
curl -X POST https://api.brevo.com/v3/contacts/doubleOptinConfirmation \
-H "api-key: $BREVO_API_KEY" \
-H "content-type: application/json" \
-d '{
"email": "[email protected]",
"includeListIds": [18],
"templateId": 156,
"redirectionUrl": "https://example.com/newsletter-bestaetigt/"
}'
Warum das überhaupt durchrutscht
Brevo’s offizielle API-Reference dokumentiert {{ params.DOUBLE_OPT_IN }} als den Pflicht-Platzhalter. Diese Doku ist veraltet. Die korrekte Variable seit der Brevo-Template-Engine-Umstellung steht im Hilfe-Center-Artikel zur DOI-Personalisierung — {{ doubleoptin }}. Wer den falschen Platzhalter nutzt, bekommt HTTP 204, die Mail wird versendet, der HTML-Link bleibt leer. Im Plaintext-Teil generiert Brevo den DOI-Link separat aus einem internen Default-Template, daher fällt der Fehler oft erst auf, wenn ein Empfänger ihn meldet.
Zwei Doku-Quellen desselben Anbieters mit widersprüchlichen Angaben, eine davon falsch — das ist nicht Brevo-spezifisch. Wir sehen das regelmäßig bei Drittsystemen, deren Marketing-Doku schneller aktualisiert wird als die technische Reference, oder umgekehrt.
Drei Lehren für vergleichbare Setups
Erstens: API-Doku ist nicht immer die letzte Wahrheit. Bei Brevo widersprechen sich API-Reference und Hilfe-Center. Die Hilfe-Center-Variante war die korrekte. Bei Bugs in Drittsystemen lohnt sich der Blick in die nicht-technische Doku des Anbieters — also Help-Center, Community-Threads, Status-Pages.
Zweitens: HTTP 2xx ist kein Beweis dafür, dass die Sache funktioniert. Brevo bestätigte erfolgreichen Versand mit HTTP 204, der Empfänger bekam einen unbenutzbaren Button. Newsletter- und Transactional-Setups gehören End-to-End getestet, nicht nur auf API-Response-Codes geprüft.
Drittens: Plaintext und HTML auseinanderhalten. Brevo füllt beim DOI den Plaintext-Teil aus einem eigenen Default-Template. Wer nur HTML-Clients zum Testen verwendet, sieht den Bug; wer in einem reinen Plaintext-Client testet, übersieht ihn. Bestätigungs-Mails in mindestens zwei Clients öffnen, einer davon mit erzwungenem Plaintext-Modus.
Wer das gerade selbst sucht
Wenn Sie an einem Newsletter-Funnel arbeiten und der DOI-Klick ins Leere führt, ist die Wahrscheinlichkeit hoch, dass es genau dieser Platzhalter ist. Ersetzen Sie {{ params.DOUBLE_OPT_IN }} durch {{ doubleoptin }}, ein Test-DOI durchlaufen lassen, fertig.
Falls Sie unsicher sind, ob Ihr Newsletter-Setup sauber durchläuft — von der API-Antwort über die Template-Engine bis zur Empfänger-Inbox: wir prüfen das in einem 20-Minuten-Klartext-Call, ohne Verpflichtung. Direkt mit Julian Raab, kein Junior dazwischen. Termin vereinbaren →