27-Apr-2026 — Brevo unsubscribes & blocked-contacts management #18

build 624commit 78478d7


What's new

Highlights

Live view of Brevo's blocklist

Member-level visibility

One-click cleanup

Bulk-import safety

Single-pass list assignment on bulk import

Branded unsubscribe page

List-scoped unsubscribes, activity feed, and clear-all

Unsubscribes view polish

Automation, not ad-hoc

Help content seeded

Salesforce writeback ready, not yet wired

Member bulk-load Upload History polish

Other

server: serve index.html no-cache and 404 missing static assets

After a deploy, tabs that had a stale index.html (referencing previous content-hashed bundles like main-GIDJEFWQ.js) would silently fail to boot. The SPA catch-all was happily serving index.html for the missing .js request, which tripped strict MIME checking ("Expected JavaScript, got text/html"), leaving a blank screen until the user thought to hard-refresh.

Hashed bundle/css/font/image assets remain safely long-cacheable via their content hashes - only the HTML entry point changes caching.

Brevo Unsubscribes & Blocked Contacts

A new admin page at Mail Settings → Unsubscribes that gives committee members complete visibility of Brevo's suppression state — every contact who unsubscribed via an email link, the Brevo unsubscribe page, the Brevo API, plus contacts blocked by an admin, hard-bounced or flagged as spam.

Two views

The toggle at the top-right switches between:

Unsubscribes tab — member-level unsubscribe events from Brevo, with reason badges and the originating list

What each row shows

Filters

Blocks tab — Brevo's account-level blocklist (hard bounces, spam flags, admin blocks)

Side effects of opening the page

Each row that matches an NGX member writes a mailListAudit row keyed by (memberId, listId, blockedAt) so the block becomes part of that member's permanent audit trail (visible on the Brevo tab of the member admin modal). Each matched member's emailBlock summary is updated to reflect the latest Brevo state, and the contact's NGX mail.subscriptions are flipped to unsubscribed for the lists Brevo holds them on.

Actions

Remove from blocklist (trash icon) calls Brevo's DELETE /smtp/blockedContacts/{email}, then clears the matched member's local emailBlock and writes an audit row recording the manual override. Subscription state on the member is not changed automatically — re-subscribe explicitly via the member's Brevo tab if appropriate.

The Options dropdown also exposes a Clear all blocks action that removes every contact from Brevo's blocklist and clears every local emailBlock record. NGX member records are not deleted.

Automation

Bulk-import safety

When a Ramblers HQ CSV is re-imported, members already flagged with emailBlock are still updated, but they will not be auto-subscribed to mail lists regardless of what HQ's Email Marketing Consent column says. The local block is the source of truth for outbound deliverability.

admin: rsm-style restyle of admin pages with mobile-friendly tables (#18)