# 23-Apr-2026 — Brevo re-send mode for worker-based forwarding

## GitHub [#130](https://github.com/nbarrett/ngx-ramblers/issues/130) — [commit 225a60c](https://github.com/nbarrett/ngx-ramblers/commit/225a60c494f2710238cffe479209dee9ba479c36)

_____

Inbound mail forwarded via the Cloudflare Worker was being quarantined or silently dropped by strict recipient providers (Microsoft 365 and similar) because plain forwarding breaks DKIM and misaligns SPF. This release adds an alternative, admin-selectable **Brevo re-send** delivery mode that DKIM-signs the onward hop against the group domain.

## Why plain forwarding fails

- **DKIM breaks** — the worker rewrites envelope headers covered by the original signature, so the downstream hash check fails.
- **SPF misaligns** — the onward sender is our worker, not the domain authorised to send for the original `From:` address.
- **Strict providers drop silently** — Microsoft 365 quarantines without bouncing, making failures invisible to committee members.

## Admin UI

Each committee role picks its delivery mode independently. The legacy **Cloudflare forward** stays available for low-friction setups; **Brevo re-send** is marked *Recommended* for any role that has seen delivery trouble.

### Full role-edit screen

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/c796dcd4-3d5e-4c8f-af01-5362c7dace9c.png)

*The shared-inbox banner at the top confirms which committee members a role fans out to. The delivery-method radios have plain-English helper text and an **Active** strip showing the live worker.*

### Selecting Brevo re-send on a fresh role

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/eab11e56-942b-42c0-9aeb-887977e873ce.png)

**Not yet active — 3 committee members configured — mail won't be delivered until the Worker is deployed.* The **Deploy Worker** button creates the worker from the Brevo re-send template and wires it to the HMAC webhook.*

### After Deploy

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/0c87bac6-6784-43fc-be32-3cee49086d53.png)

*Worker is live. **Update Worker** is greyed out because the deployed script matches what the generator would produce today — it self-enables later if a template change introduces drift.*

### Switching an already-live role back to Cloudflare forward

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/079b802b-973e-48a5-8b86-59cafcae00a4.png)

*The radios have diverged from the live worker, so an inline **Apply delivery method change** CTA appears with a *Currently live: Brevo re-send. Click to switch.* hint. Nothing is redeployed until the admin clicks it.*

### After Apply

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/cd547c21-16f7-4c90-9a98-23ade2111de4.png)

*Worker has been redeployed from the Cloudflare forward template. The **Active** strip now reads *via Cloudflare forward*.*

## How Brevo re-send works

1. The Cloudflare Worker captures the raw MIME of the inbound message.
2. It POSTs to `/api/cloudflare/email-routing/inbound-mime` over HMAC-signed JSON.
3. NGX verifies the HMAC, then rewrites **only** the envelope headers it must touch (`From`, `Reply-To`, `To` and the authentication-related headers). HTML body, inline images, attachments, `multipart/alternative` and boundaries are preserved byte-for-byte.
4. NGX relays via Brevo SMTP, which DKIM-signs against the group domain.

Because Brevo is the sending authority for the group domain, the onward hop is SPF-aligned and DKIM-valid end-to-end. Rich HTML, inline images and alternative parts render exactly as the sender composed them.


## Other fixes shipping with this change

- **Drift detection** — a new `GET /workers/:scriptName/info` endpoint fetches the deployed script, regenerates it from the current template, and byte-compares. When they differ, the Update Worker button self-enables and the first differing byte range is logged.
- **Verification list collapsed** — unverified recipients now render as a single "X recipients need verification" alert instead of repeating per-recipient blocks.
- **Subject column added** to the Email Routing Log, fed by a new GraphQL field on the Cloudflare log query.
- **SMTP Login and SMTP Key fields** on the Mail API Settings page, each with a "View in Brevo" deep-link button.
- **Per-environment auto-config** — the inbound webhook secret and URL are derived from `systemConfig()` on first use; only SMTP credentials need entering per env.
- **Image-detection fix** — markdown image syntax and `<img>` tags quoted inside code spans or fenced code blocks no longer trigger a false 📸 marker on the release-notes index.