{"id":"6a33f42fc5177ca4223c7a1b","title":"2026 06 18","path":"how-to/committee/release-notes/2026-06-18","contentMarkdown":"# 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)\n\n## [build 702](https://github.com/nbarrett/ngx-ramblers/actions/runs/27762328900) — [commit 4b2fd87](https://github.com/nbarrett/ngx-ramblers/commit/4b2fd87dd0da5782be88abd525cfd8334ccde300)\n\n_____\n\n## Campaign \"Sent to\" footer showed the sender's address for every recipient\n\nThe 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`.\n\n- 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`.\n- 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.\n- Forwarding `campaign` to both `renderBrandedTemplate` calls restores per-recipient `{{contact.EMAIL}}` in the footer.\n\n## Whole-list sends now run their post-send actions\n\nThe disable / bulk-delete post-send actions previously only ran on the transactional selected-members path; a whole-list campaign send never applied them.\n\n- 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).\n- A shared `applyPostSendActionsToMembers` controller now backs both the batch and campaign paths.\n- New authenticated `POST /member/apply-post-send-actions` route and `memberService.applyPostSendActions`.\n- 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.\n- 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).\n\n## Inbox push (Pub/Sub) setup is clearer about its topic\n\nEnabling 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\".\n\n- The push-config endpoint now returns the configured topic name (`InboxPushConfigResponse.configuredTopicName`), read from `googleInbox.pubsubTopicName`.\n- 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.\n- 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.\n\n## \"Clear all forwards\" now deletes the live Cloudflare rules, scoped and confirmed\n\nOn 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\".\n\n- 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.\n- 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.\n- 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.","contentHtml":"<h1>18-Jun-2026 — campaign recipient address, per-list post-send actions, Pub/Sub guidance, and scoped cl... <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/278\">#278</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/27762328900\">build 702</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/4b2fd87dd0da5782be88abd525cfd8334ccde300\">commit 4b2fd87</a></h2>\n<hr>\n<h2>Campaign &quot;Sent to&quot; footer showed the sender&#39;s address for every recipient</h2>\n<p>The branded email layout footer renders <code>Sent to {{params.memberMergeFields.EMAIL}}</code>. For campaigns these <code>params</code> tokens must be rewritten to <code>{{contact.EMAIL}}</code> so Brevo fills in each recipient&#39;s own address, and that rewrite only runs when <code>renderBrandedTemplate</code> is called with <code>campaign = true</code>.</p>\n<ul>\n<li>Originally fixed in #279 (<code>fc4d8894</code>), then regressed in #273 (<code>10c1d92e</code>, version-controlled email templates retiring Brevo as a template store): the rewritten <code>emailRequest.body</code> / <code>localTemplateHtml</code> branches in <code>performTemplateSubstitution</code> dropped the <code>campaign</code> argument, so it defaulted to <code>false</code>.</li>\n<li>Campaign sends then stopped converting params → contact, and the footer&#39;s EMAIL was substituted at build time with the campaign&#39;s single <code>params</code> value — the logged-in sender — baked identically into every recipient&#39;s copy.</li>\n<li>Forwarding <code>campaign</code> to both <code>renderBrandedTemplate</code> calls restores per-recipient <code>{{contact.EMAIL}}</code> in the footer.</li>\n</ul>\n<h2>Whole-list sends now run their post-send actions</h2>\n<p>The disable / bulk-delete post-send actions previously only ran on the transactional selected-members path; a whole-list campaign send never applied them.</p>\n<ul>\n<li>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).</li>\n<li>A shared <code>applyPostSendActionsToMembers</code> controller now backs both the batch and campaign paths.</li>\n<li>New authenticated <code>POST /member/apply-post-send-actions</code> route and <code>memberService.applyPostSendActions</code>.</li>\n<li>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.</li>\n<li>The email composer auto-fills the recipient selection when a narrow list is active (<code>autoFill</code> now bound to <code>state.narrowListId !== null</code> rather than always false).</li>\n</ul>\n<h2>Inbox push (Pub/Sub) setup is clearer about its topic</h2>\n<p>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 &quot;topic not found&quot;.</p>\n<ul>\n<li>The push-config endpoint now returns the configured topic name (<code>InboxPushConfigResponse.configuredTopicName</code>), read from <code>googleInbox.pubsubTopicName</code>.</li>\n<li>The mailbox-connections page shows whether a topic is set up and points back to step 1 (&quot;Run Google Cloud setup&quot;) when it is not, and pre-fills a mailbox&#39;s topic from the configured one when switching it to WATCH.</li>\n<li>WATCH-mode failures now explain that the topic must exist and grant <a href=\"mailto:gmail-api-push@system.gserviceaccount.com\">gmail-api-push@system.gserviceaccount.com</a> the Publisher role, with the steps to put it right.</li>\n</ul>\n<h2>&quot;Clear all forwards&quot; now deletes the live Cloudflare rules, scoped and confirmed</h2>\n<p>On the committee email settings, &quot;Clear all forwards&quot; previously only set every role&#39;s <code>forwardEmailTarget</code> to CATCHALL in NGX&#39;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 &quot;everything via the catch-all&quot;.</p>\n<ul>\n<li>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.</li>\n<li>Deletion is scoped to the current site&#39;s own domain/subdomain: <code>domainForwardRules()</code> selects only rules whose literal <code>to</code> matcher address ends in <code>@&lt;baseDomain&gt;</code>. Sibling subdomains that share a Cloudflare zone, and the catch-all rule itself, are left untouched.</li>\n<li>On confirm it deletes each matched rule via the same <code>deleteRule</code> path the per-role &quot;Delete Forward&quot; 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.</li>\n</ul>\n"}