{"id":"69fb87632d3be50e28f7c740","title":"2026 05 02","path":"how-to/committee/release-notes/2026-05-02","contentMarkdown":"# 02-May-2026 — Inbound Forwarding warnings + Committee Settings catch-all admin [closes #256](https://github.com/nbarrett/ngx-ramblers/issues/256)\n\n## [build 637](https://github.com/nbarrett/ngx-ramblers/actions/runs/25450246053) — [commit 4147694](https://github.com/nbarrett/ngx-ramblers/commit/4147694c1d909727dc0a320d1ce609f136bdcee5)\n\n_____\n\n## Part 1 — Inbound Forwarding warnings\n\nTwo alert-warning banners on the Inbound Forwarding tab, both rendered before the rule UI:\n\n- **Domain mismatch**: fires when the site's primary host (from `systemConfig.group.href`) is not under the deployed Cloudflare zone's apex domain. This catches the Winchester-style failure where a per-env `zoneId` was empty, the deploy fell back to the global `zoneId`, and rules were silently created in the wrong zone.\n- **Stale deploy**: fires when a per-env `zoneId` has been set in the staging `environments` doc but the running app's deployed `CLOUDFLARE_CONFIG` still has a different `zoneId` baked in (i.e. needs a redeploy).\n\nThe resolution chain in `cloudflare-config.ts` is unchanged; the new fields (`cloudflareZoneBaseDomain`, `primaryHost`, `perEnvironmentDbZoneId`) are added to `nonSensitiveCloudflareConfig()` purely to drive the warnings. Per-env `zoneId` is looked up by stripping the `ngx-ramblers-` prefix from `APP_NAME` and matching the `environments[]` entry.\n\nSuffix matching uses a new `isHostUnderDomain()` helper in `projects/ngx-ramblers/src/app/functions/hosts.ts` (apex or subdomain, case-insensitive, strips a leading `www.`). Spec covers the apex, subdomain, `www-prefix`, the deceptive `notngx-ramblers.org.uk` non-match, and missing inputs.\n\n## Part 2 — Manage zone catch-all rule from the Committee Settings admin\n\nToday the catch-all rule for a zone (e.g. `*@ngx-ramblers.org.uk` -> `nick.barrett36@gmail.com`) is only configurable from Cloudflare's own dashboard. This brings it into NGX so the same admin surface that manages per-role rules also handles the catch-all, and adds the worker-based fan-out for multi-recipient catch-all delivery.\n\nA new **\"Catch-all rule for `*@<baseDomain>`\"** card sits on the Committee Members tab of Committee Settings, between the action toolbar and the role table. It shows the current state as a status badge (Disabled / Drop / Forward -> `<addr>` / Multiple recipients (Worker)) and an **Edit** button. The editor exposes an action selector with four choices and a destination input that switches shape based on the action: a single email field for Forward, an add/remove list for Multiple. Worker-backed catch-all rules pre-load their existing recipients when editing so the operator sees what's currently configured rather than starting blank.\n\nServer side adds an `updateCatchAllRule` helper that `PUT`s to `/zones/<zoneId>/email/routing/rules/catch_all`, plus a `PUT /api/cloudflare/email-routing/rules/catch-all` route. The route resolves the request shape via a `planCatchAllRule` helper that uploads a worker named `email-fwd-<zone>-catch-all` for the **Multiple** action (reusing the existing worker template machinery and `BREVO_RESEND` mode wiring from per-role workers) and tears down any previously-bound worker when switching to Drop, Disabled, or Forward. Rule names follow the existing `\"<Mode label> - <description>\"` convention so they read sensibly in Cloudflare's UI alongside per-role rules.\n\nFrontend service exposes `updateCatchAllRule` which invalidates the relevant caches and pushes the new rule onto the existing `catchAllNotifications` `BehaviorSubject` so the badge refreshes after a save.\n\nNew model types: `CatchAllAction` enum, `UpdateCatchAllRequest` interface, `CatchAllResolution` interface (the latter for use by Part 3's catch-all-aware role view).\n\n727/727 frontend tests pass.","contentHtml":"<h1>02-May-2026 — Inbound Forwarding warnings + Committee Settings catch-all admin <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/256\">closes #256</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/25450246053\">build 637</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/4147694c1d909727dc0a320d1ce609f136bdcee5\">commit 4147694</a></h2>\n<hr>\n<h2>Part 1 — Inbound Forwarding warnings</h2>\n<p>Two alert-warning banners on the Inbound Forwarding tab, both rendered before the rule UI:</p>\n<ul>\n<li><strong>Domain mismatch</strong>: fires when the site&#39;s primary host (from <code>systemConfig.group.href</code>) is not under the deployed Cloudflare zone&#39;s apex domain. This catches the Winchester-style failure where a per-env <code>zoneId</code> was empty, the deploy fell back to the global <code>zoneId</code>, and rules were silently created in the wrong zone.</li>\n<li><strong>Stale deploy</strong>: fires when a per-env <code>zoneId</code> has been set in the staging <code>environments</code> doc but the running app&#39;s deployed <code>CLOUDFLARE_CONFIG</code> still has a different <code>zoneId</code> baked in (i.e. needs a redeploy).</li>\n</ul>\n<p>The resolution chain in <code>cloudflare-config.ts</code> is unchanged; the new fields (<code>cloudflareZoneBaseDomain</code>, <code>primaryHost</code>, <code>perEnvironmentDbZoneId</code>) are added to <code>nonSensitiveCloudflareConfig()</code> purely to drive the warnings. Per-env <code>zoneId</code> is looked up by stripping the <code>ngx-ramblers-</code> prefix from <code>APP_NAME</code> and matching the <code>environments[]</code> entry.</p>\n<p>Suffix matching uses a new <code>isHostUnderDomain()</code> helper in <code>projects/ngx-ramblers/src/app/functions/hosts.ts</code> (apex or subdomain, case-insensitive, strips a leading <code>www.</code>). Spec covers the apex, subdomain, <code>www-prefix</code>, the deceptive <code>notngx-ramblers.org.uk</code> non-match, and missing inputs.</p>\n<h2>Part 2 — Manage zone catch-all rule from the Committee Settings admin</h2>\n<p>Today the catch-all rule for a zone (e.g. <code>*@ngx-ramblers.org.uk</code> -&gt; <code>nick.barrett36@gmail.com</code>) is only configurable from Cloudflare&#39;s own dashboard. This brings it into NGX so the same admin surface that manages per-role rules also handles the catch-all, and adds the worker-based fan-out for multi-recipient catch-all delivery.</p>\n<p>A new <strong>&quot;Catch-all rule for <code>*@&lt;baseDomain&gt;</code>&quot;</strong> card sits on the Committee Members tab of Committee Settings, between the action toolbar and the role table. It shows the current state as a status badge (Disabled / Drop / Forward -&gt; <code>&lt;addr&gt;</code> / Multiple recipients (Worker)) and an <strong>Edit</strong> button. The editor exposes an action selector with four choices and a destination input that switches shape based on the action: a single email field for Forward, an add/remove list for Multiple. Worker-backed catch-all rules pre-load their existing recipients when editing so the operator sees what&#39;s currently configured rather than starting blank.</p>\n<p>Server side adds an <code>updateCatchAllRule</code> helper that <code>PUT</code>s to <code>/zones/&lt;zoneId&gt;/email/routing/rules/catch_all</code>, plus a <code>PUT /api/cloudflare/email-routing/rules/catch-all</code> route. The route resolves the request shape via a <code>planCatchAllRule</code> helper that uploads a worker named <code>email-fwd-&lt;zone&gt;-catch-all</code> for the <strong>Multiple</strong> action (reusing the existing worker template machinery and <code>BREVO_RESEND</code> mode wiring from per-role workers) and tears down any previously-bound worker when switching to Drop, Disabled, or Forward. Rule names follow the existing <code>&quot;&lt;Mode label&gt; - &lt;description&gt;&quot;</code> convention so they read sensibly in Cloudflare&#39;s UI alongside per-role rules.</p>\n<p>Frontend service exposes <code>updateCatchAllRule</code> which invalidates the relevant caches and pushes the new rule onto the existing <code>catchAllNotifications</code> <code>BehaviorSubject</code> so the badge refreshes after a save.</p>\n<p>New model types: <code>CatchAllAction</code> enum, <code>UpdateCatchAllRequest</code> interface, <code>CatchAllResolution</code> interface (the latter for use by Part 3&#39;s catch-all-aware role view).</p>\n<p>727/727 frontend tests pass.</p>\n"}