# 18-Jun-2026 — campaign recipient address, per-list post-send actions, Pub/Sub guidance, and scoped cl... [#278](https://github.com/nbarrett/ngx-ramblers/issues/278)

## [build 702](https://github.com/nbarrett/ngx-ramblers/actions/runs/27762328900) — [commit 4b2fd87](https://github.com/nbarrett/ngx-ramblers/commit/4b2fd87dd0da5782be88abd525cfd8334ccde300)

_____

## Campaign "Sent to" footer showed the sender's address for every recipient

The branded email layout footer renders `Sent to {{params.memberMergeFields.EMAIL}}`. For campaigns these `params` tokens must be rewritten to `{{contact.EMAIL}}` so Brevo fills in each recipient's own address, and that rewrite only runs when `renderBrandedTemplate` is called with `campaign = true`.

- Originally fixed in #279 (`fc4d8894`), then regressed in #273 (`10c1d92e`, version-controlled email templates retiring Brevo as a template store): the rewritten `emailRequest.body` / `localTemplateHtml` branches in `performTemplateSubstitution` dropped the `campaign` argument, so it defaulted to `false`.
- Campaign sends then stopped converting params → contact, and the footer's EMAIL was substituted at build time with the campaign's single `params` value — the logged-in sender — baked identically into every recipient's copy.
- Forwarding `campaign` to both `renderBrandedTemplate` calls restores per-recipient `{{contact.EMAIL}}` in the footer.

## Whole-list sends now run their post-send actions

The disable / bulk-delete post-send actions previously only ran on the transactional selected-members path; a whole-list campaign send never applied them.

- After a campaign send, every group member subscribed to the list has the configured post-send actions applied (disable group member and/or cascade delete).
- A shared `applyPostSendActionsToMembers` controller now backs both the batch and campaign paths.
- New authenticated `POST /member/apply-post-send-actions` route and `memberService.applyPostSendActions`.
- The pre-send warning now counts the whole list (it was showing 0 in list mode, only counting individually selected members), and the campaign success message reports how many members were removed or disabled.
- The email composer auto-fills the recipient selection when a narrow list is active (`autoFill` now bound to `state.narrowListId !== null` rather than always false).

## Inbox push (Pub/Sub) setup is clearer about its topic

Enabling real-time WATCH delivery on a mailbox needs a Pub/Sub topic that already exists and lets Gmail publish to it. The admin page gave no hint of which topic, and a missing topic failed with a raw "topic not found".

- The push-config endpoint now returns the configured topic name (`InboxPushConfigResponse.configuredTopicName`), read from `googleInbox.pubsubTopicName`.
- The mailbox-connections page shows whether a topic is set up and points back to step 1 ("Run Google Cloud setup") when it is not, and pre-fills a mailbox's topic from the configured one when switching it to WATCH.
- WATCH-mode failures now explain that the topic must exist and grant gmail-api-push@system.gserviceaccount.com the Publisher role, with the steps to put it right.

## "Clear all forwards" now deletes the live Cloudflare rules, scoped and confirmed

On the committee email settings, "Clear all forwards" previously only set every role's `forwardEmailTarget` to CATCHALL in NGX's own config; it issued no Cloudflare deletes. The live per-role forwarding rules stayed active, and because Cloudflare matches literal rules before the catch-all, they kept diverting role mail away from the catch-all inbox — so the button did not actually achieve "everything via the catch-all".

- Clicking the button now opens an inline confirmation that states how many live Cloudflare forwarding rules will be deleted before anything happens, names the catch-all destination they will route to instead, and offers Cancel.
- Deletion is scoped to the current site's own domain/subdomain: `domainForwardRules()` selects only rules whose literal `to` matcher address ends in `@<baseDomain>`. Sibling subdomains that share a Cloudflare zone, and the catch-all rule itself, are left untouched.
- On confirm it deletes each matched rule via the same `deleteRule` path the per-role "Delete Forward" button uses, sets the non-vacant roles to CATCHALL, reloads the live rules, saves the committee config, and reports the count deleted. Worker-backed rules removed this way leave their script behind, which remains visible under the existing Orphaned Cloudflare Workers section for separate cleanup.