{"id":"69eabcded114cbdda8237d3c","title":"2026 04 24","path":"how-to/committee/release-notes/2026-04-24","contentMarkdown":"# 24-Apr-2026 — SPF/DMARC management, plain-text alternatives, List-Unsubscribe header\n\n## GitHub [#186](https://github.com/nbarrett/ngx-ramblers/issues/186) — [commit 77db1d9](https://github.com/nbarrett/ngx-ramblers/commit/77db1d9fbe0b9c925c198ddca31ef6ec82806c46)\n\n_____\n\nThree deliverability fixes for transactional email, headlined by a new **Email Authentication** panel on the Brevo Senders page that lets admins fix SPF and DMARC with one click.\n\n## Email Authentication panel\n\nThe new panel sits above the existing MX records panel and shows colour-coded badges for SPF and DMARC, the current record contents, and a **Fix Auth Records** button that merges missing includes or creates the records. Existing policies are never widened; multi-record SPF states disable the button with a tooltip pointing at manual consolidation.\n\n### SPF missing the Brevo include\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/2c0fb82e-b576-4806-bf9e-247fcb7550d1.png)\n\n*ashfordramblers.org.uk — SPF exists for the Cloudflare mail path but misses `spf.brevo.com`. DMARC is already in place and is left untouched.*\n\n### Stricter existing DMARC\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/ea2b9a7b-7fcf-478e-99b6-2f9d627abc2c.png)\n\n*canterburyramblers.org.uk — same SPF gap, but this domain runs `p=reject`. The fix will merge in the Brevo include while preserving the stricter policy exactly.*\n\n### SPF fixed — strict DMARC preserved\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/f059120c-d169-4145-a5f4-5efd1eb047fd.png)\n\n*canterburyramblers.org.uk after **Fix Auth Records** — SPF now contains both `_spf.mx.cloudflare.net` and `spf.brevo.com`; `p=reject` is retained.*\n\n### Fresh domain with nothing in place\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/fb4dffc5-7c30-4d52-b93d-388b59b2524a.png)\n\n*berkshire-weekend-walkers.ngx-ramblers.org.uk — nothing at all. The panel shows exactly what will be created before the button is pressed.*\n\n### After one-click fix on a fresh domain\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/817a914f-12ec-47a7-a450-702195e80677.png)\n\n*Fresh `v=spf1 include:_spf.mx.cloudflare.net include:spf.brevo.com ~all` created, plus a monitor-only `v=DMARC1; p=none` so the admin can raise enforcement later without surprise.*\n\n## Other fixes shipping with this change\n\n- **Plain-text alternative** — every transactional email now gets a `textContent` derived from HTML when the caller supplied none. HTML-only mail is a documented spam-filter penalty signal.\n- **List-Unsubscribe header** — `<mailto:replyTo?subject=unsubscribe>` is added to transactional sends unless the caller sets its own. Campaigns are unchanged; Brevo manages their unsubscribe headers natively.\n- **Migration iterates every Brevo-registered domain**, not just the env's configured `baseDomain`. Per-domain failures are logged and skipped — one bad zone can't block the rest.\n- **www-vs-apex false warning fixed** — a new shared `apexHost()` helper strips `www.` on both sides of the site/Brevo-domain comparison.\n- **Cleaner 500 responses** — a shared `errorResponse(error)` returns `{code, message}` so clients see the real cause instead of a serialised empty Error.\n- **Idempotent destination-address creation** — POSTing an existing address returns the match instead of tripping Cloudflare's \"verification sent too recently\" cooldown.","contentHtml":"<h1>24-Apr-2026 — SPF/DMARC management, plain-text alternatives, List-Unsubscribe header</h1>\n<h2>GitHub <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/186\">#186</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/77db1d9fbe0b9c925c198ddca31ef6ec82806c46\">commit 77db1d9</a></h2>\n<hr>\n<p>Three deliverability fixes for transactional email, headlined by a new <strong>Email Authentication</strong> panel on the Brevo Senders page that lets admins fix SPF and DMARC with one click.</p>\n<h2>Email Authentication panel</h2>\n<p>The new panel sits above the existing MX records panel and shows colour-coded badges for SPF and DMARC, the current record contents, and a <strong>Fix Auth Records</strong> button that merges missing includes or creates the records. Existing policies are never widened; multi-record SPF states disable the button with a tooltip pointing at manual consolidation.</p>\n<h3>SPF missing the Brevo include</h3>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/2c0fb82e-b576-4806-bf9e-247fcb7550d1.png\" alt=\"\"></p>\n<p><em>ashfordramblers.org.uk — SPF exists for the Cloudflare mail path but misses <code>spf.brevo.com</code>. DMARC is already in place and is left untouched.</em></p>\n<h3>Stricter existing DMARC</h3>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/ea2b9a7b-7fcf-478e-99b6-2f9d627abc2c.png\" alt=\"\"></p>\n<p><em>canterburyramblers.org.uk — same SPF gap, but this domain runs <code>p=reject</code>. The fix will merge in the Brevo include while preserving the stricter policy exactly.</em></p>\n<h3>SPF fixed — strict DMARC preserved</h3>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/f059120c-d169-4145-a5f4-5efd1eb047fd.png\" alt=\"\"></p>\n<p><em>canterburyramblers.org.uk after <strong>Fix Auth Records</strong> — SPF now contains both <code>_spf.mx.cloudflare.net</code> and <code>spf.brevo.com</code>; <code>p=reject</code> is retained.</em></p>\n<h3>Fresh domain with nothing in place</h3>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/fb4dffc5-7c30-4d52-b93d-388b59b2524a.png\" alt=\"\"></p>\n<p><em>berkshire-weekend-walkers.ngx-ramblers.org.uk — nothing at all. The panel shows exactly what will be created before the button is pressed.</em></p>\n<h3>After one-click fix on a fresh domain</h3>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/817a914f-12ec-47a7-a450-702195e80677.png\" alt=\"\"></p>\n<p><em>Fresh <code>v=spf1 include:_spf.mx.cloudflare.net include:spf.brevo.com ~all</code> created, plus a monitor-only <code>v=DMARC1; p=none</code> so the admin can raise enforcement later without surprise.</em></p>\n<h2>Other fixes shipping with this change</h2>\n<ul>\n<li><strong>Plain-text alternative</strong> — every transactional email now gets a <code>textContent</code> derived from HTML when the caller supplied none. HTML-only mail is a documented spam-filter penalty signal.</li>\n<li><strong>List-Unsubscribe header</strong> — <code>&lt;mailto:replyTo?subject=unsubscribe&gt;</code> is added to transactional sends unless the caller sets its own. Campaigns are unchanged; Brevo manages their unsubscribe headers natively.</li>\n<li><strong>Migration iterates every Brevo-registered domain</strong>, not just the env&#39;s configured <code>baseDomain</code>. Per-domain failures are logged and skipped — one bad zone can&#39;t block the rest.</li>\n<li><strong>www-vs-apex false warning fixed</strong> — a new shared <code>apexHost()</code> helper strips <code>www.</code> on both sides of the site/Brevo-domain comparison.</li>\n<li><strong>Cleaner 500 responses</strong> — a shared <code>errorResponse(error)</code> returns <code>{code, message}</code> so clients see the real cause instead of a serialised empty Error.</li>\n<li><strong>Idempotent destination-address creation</strong> — POSTing an existing address returns the match instead of tripping Cloudflare&#39;s &quot;verification sent too recently&quot; cooldown.</li>\n</ul>\n"}