{"id":"6a30b9e7f7bb7cf5d2524bec","title":"2026 06 16","path":"how-to/committee/release-notes/2026-06-16","contentMarkdown":"# 16-Jun-2026 — inbox-aware email routing and Contact Us delivery [#278](https://github.com/nbarrett/ngx-ramblers/issues/278)\n\n## [build 698](https://github.com/nbarrett/ngx-ramblers/actions/runs/27590219957) — [commit 0fa5561](https://github.com/nbarrett/ngx-ramblers/commit/0fa55617496e02503b37568e37ac0013994814bf)\n\n_____\n\n> 📖 New to this feature? See [Setting up a Gmail inbox for committee replies](https://ngx-ramblers.org.uk/how-to/technical-articles/2026-05-29-gmail-inbox-setup) for the step-by-step setup guide.\n\nReworks how committee roles route email - outbound senders, inbound\nforwarding, and the Contact Us form - so the admin screens describe what\nactually happens for each group's setup: a connected Gmail inbox, a\nCloudflare catch-all, fan-out to several people, or individual personal\naddresses. Part of the Gmail inbox work in #278.\n\n## What's new for committee admins\n\n### Contact Us routing, per role\n\nEach role has a dedicated Contact Us tab controlling where contact-us form\nsubmissions for that role are sent, separate from the role's Inbound\nForwarding.\n\n- New option \"The role's own address\": contact-us is sent to the role\naddress (e.g. walks@yourdomain.org.uk) and then follows that role's\nInbound Forwarding, so it reaches the same place as the role's normal\nmail with no extra setup.\n- Automatic handling when the role uses a connected inbox: the tab\ncollapses to a short read-only note instead of a dropdown, because there\nis nothing to configure - contact-us rides the role's forwarding into the\nconnected inbox and appears grouped under the role.\n- The \"How this delivers\" summary describes the real path for each option\n(\"follows this role's Inbound Forwarding to wherever that's configured\")\nrather than assuming a connected inbox, so it is correct for groups that\nforward to personal addresses or to several recipients.\n- The Contact Us help text now lists all routing options and explains the\nconnected-inbox automatic handling.\n- The display name override and the copyable markdown contact link remain\non every role.\n\n### Inbound forwarding clarity\n\n- Connected Gmail inboxes appear as choices in a role's Inbound Forwarding.\n- Catch-all routing is described correctly even when the catch-all is a\nWorker fan-out, showing \"Multiple recipients (Worker)\" instead of wrongly\nreporting that no catch-all rule exists.\n- The \"link a member\" prompt only appears for the member's-personal-email\ntarget, where linking a member is the right action; Catchall, Custom and\nNo forwarding no longer show it.\n- A forwarding destination awaiting Cloudflare verification is shown as an\namber \"Verification needed\" notice rather than a red error.\n\n### Contact Us system role\n\n- No Contact Us tab (a role cannot contact itself).\n- Inbound Forwarding no longer offers \"Member's personal email\" (there is\nno member to link); it defaults to \"No forwarding\" and can be set to\nCustom, Multiple recipients, or Catchall.\n- The \"System role\" notice now shows the standard warning icon.\n- It cannot be deleted: the delete button is disabled with a tooltip\nexplaining it is the contact-us sender linked across the site. Other\nsystem roles (e.g. Info) are deletable.\n\n### Committee roles table\n\n- Email addresses stay on a single line instead of wrapping mid-address.\n- Name, Role and Description columns are sized so descriptions get room and\nnothing is cramped; the status columns are sized to their content and the\nsort carets no longer wrap onto a second line.\n\n### Email routing status\n\n- The \"Active\" tooltip shows the role's configured address for multi-role\nmembers, instead of one long address built by joining all of a member's\nrole names together.\n- The \"Catch-all\" and \"Multiple\" badge tooltips describe their destinations\nin the same style, e.g. \"Routed via catch-all -> 4 recipients\" and\n\"info@yourdomain.org.uk -> 4 recipients\".\n\n### Gmail inbox setup (External systems -> Mail)\n\n- The Pub/Sub setup now shows only the Google Cloud project ID; the topic\nname is hidden and defaulted internally, since the setup creates it.\n- The project ID persists when you save the configuration, with a note that\nit is not active until you Run Google Cloud setup.\n- The setup help text is reworded and no longer references the hidden topic\nfield; the success message now has a \"Google Cloud setup complete\" title\nand a tick icon.\n- The \"Clear OAuth client\" and per-mailbox \"Remove\" buttons are grey by\ndefault and red on hover; the \"Apply push\" button and the help text under\nfields have proper spacing.\n\n## Technical details\n\n- New outbound/contact-us target ForwardEmailTarget.ROLE_EMAIL, resolved\nserver-side to the role's own address without rewriting it\n(server/lib/contact-us/resolve-and-send.ts, committee.model.ts).\n- Connected-inbox detection roleForwardsToConnectedInbox() drives the\nContact Us collapse and forces contactUsTarget to ROLE_EMAIL so the saved\nvalue matches the displayed behaviour (committee-member.ts).\n- catchAllExists()/catchAllSummary() and catchAllTooltipDestination()\nrecognise Worker catch-alls; the \"not yet configured\" status is gated to\nthe member-email target only (committee-member.ts, email-routing-status.ts).\n- The Active badge tooltip uses roleEmailFor(role) (configured email,\nfalling back to type@domain) for consistency with the routing status\nlogic; removed the unused workerScriptNameFor() method\n(committee-settings.ts).\n- Roles table switched to natural (auto) layout with a non-wrapping email\ncell and content-sized status columns (committee-settings.ts).\n- Server Cloudflare email-routing endpoints share a single asyncRoute\nwrapper and HttpError type, with a shared pre-flight that creates and\nreports unverified forwarding destinations (server/lib/shared/async-route.ts,\nserver/lib/shared/http-error.ts, cloudflare-email-routing-routes.ts).\n- Client error display standardised via extractErrorMessage; verification\n-pending responses shown as an amber notice.\n- New migration 20260616000000-update-committee-contact-us-help upserts the\nrefreshed committee-contact-us-help content text so it updates on every\nenvironment at deploy; default-content.ts kept in sync.\n- StoredValue keys renamed to match their kebab on-screen values and sorted,\nwith no behaviour change (ui-actions.ts and consumers).\n- Gmail inbox settings: Project ID binds to googleInbox.pubsubProjectId so it\npersists on Save; topic-name field removed and defaulted; runSetup reads the\nproject ID from config; statusTitle drives the success alert title + icon\n(system-gmail-inbox-settings.ts).\n- New btn-grey-danger style (grey default, red hover) applied to Clear OAuth\nclient and Remove (buttons.sass and the two external-systems components).\n- Committee roles delete restricted to non-Contact-Us roles via\nisContactUsSystemRole(); Contact Us shows a disabled delete with tooltip\n(committee-settings.ts).","contentHtml":"<h1>16-Jun-2026 — inbox-aware email routing and Contact Us delivery <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/27590219957\">build 698</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/0fa55617496e02503b37568e37ac0013994814bf\">commit 0fa5561</a></h2>\n<hr>\n<blockquote>\n<p>📖 New to this feature? See <a href=\"https://ngx-ramblers.org.uk/how-to/technical-articles/2026-05-29-gmail-inbox-setup\">Setting up a Gmail inbox for committee replies</a> for the step-by-step setup guide.</p>\n</blockquote>\n<p>Reworks how committee roles route email - outbound senders, inbound\nforwarding, and the Contact Us form - so the admin screens describe what\nactually happens for each group&#39;s setup: a connected Gmail inbox, a\nCloudflare catch-all, fan-out to several people, or individual personal\naddresses. Part of the Gmail inbox work in #278.</p>\n<h2>What&#39;s new for committee admins</h2>\n<h3>Contact Us routing, per role</h3>\n<p>Each role has a dedicated Contact Us tab controlling where contact-us form\nsubmissions for that role are sent, separate from the role&#39;s Inbound\nForwarding.</p>\n<ul>\n<li>New option &quot;The role&#39;s own address&quot;: contact-us is sent to the role\naddress (e.g. <a href=\"mailto:walks@yourdomain.org.uk\">walks@yourdomain.org.uk</a>) and then follows that role&#39;s\nInbound Forwarding, so it reaches the same place as the role&#39;s normal\nmail with no extra setup.</li>\n<li>Automatic handling when the role uses a connected inbox: the tab\ncollapses to a short read-only note instead of a dropdown, because there\nis nothing to configure - contact-us rides the role&#39;s forwarding into the\nconnected inbox and appears grouped under the role.</li>\n<li>The &quot;How this delivers&quot; summary describes the real path for each option\n(&quot;follows this role&#39;s Inbound Forwarding to wherever that&#39;s configured&quot;)\nrather than assuming a connected inbox, so it is correct for groups that\nforward to personal addresses or to several recipients.</li>\n<li>The Contact Us help text now lists all routing options and explains the\nconnected-inbox automatic handling.</li>\n<li>The display name override and the copyable markdown contact link remain\non every role.</li>\n</ul>\n<h3>Inbound forwarding clarity</h3>\n<ul>\n<li>Connected Gmail inboxes appear as choices in a role&#39;s Inbound Forwarding.</li>\n<li>Catch-all routing is described correctly even when the catch-all is a\nWorker fan-out, showing &quot;Multiple recipients (Worker)&quot; instead of wrongly\nreporting that no catch-all rule exists.</li>\n<li>The &quot;link a member&quot; prompt only appears for the member&#39;s-personal-email\ntarget, where linking a member is the right action; Catchall, Custom and\nNo forwarding no longer show it.</li>\n<li>A forwarding destination awaiting Cloudflare verification is shown as an\namber &quot;Verification needed&quot; notice rather than a red error.</li>\n</ul>\n<h3>Contact Us system role</h3>\n<ul>\n<li>No Contact Us tab (a role cannot contact itself).</li>\n<li>Inbound Forwarding no longer offers &quot;Member&#39;s personal email&quot; (there is\nno member to link); it defaults to &quot;No forwarding&quot; and can be set to\nCustom, Multiple recipients, or Catchall.</li>\n<li>The &quot;System role&quot; notice now shows the standard warning icon.</li>\n<li>It cannot be deleted: the delete button is disabled with a tooltip\nexplaining it is the contact-us sender linked across the site. Other\nsystem roles (e.g. Info) are deletable.</li>\n</ul>\n<h3>Committee roles table</h3>\n<ul>\n<li>Email addresses stay on a single line instead of wrapping mid-address.</li>\n<li>Name, Role and Description columns are sized so descriptions get room and\nnothing is cramped; the status columns are sized to their content and the\nsort carets no longer wrap onto a second line.</li>\n</ul>\n<h3>Email routing status</h3>\n<ul>\n<li>The &quot;Active&quot; tooltip shows the role&#39;s configured address for multi-role\nmembers, instead of one long address built by joining all of a member&#39;s\nrole names together.</li>\n<li>The &quot;Catch-all&quot; and &quot;Multiple&quot; badge tooltips describe their destinations\nin the same style, e.g. &quot;Routed via catch-all -&gt; 4 recipients&quot; and\n&quot;<a href=\"mailto:info@yourdomain.org.uk\">info@yourdomain.org.uk</a> -&gt; 4 recipients&quot;.</li>\n</ul>\n<h3>Gmail inbox setup (External systems -&gt; Mail)</h3>\n<ul>\n<li>The Pub/Sub setup now shows only the Google Cloud project ID; the topic\nname is hidden and defaulted internally, since the setup creates it.</li>\n<li>The project ID persists when you save the configuration, with a note that\nit is not active until you Run Google Cloud setup.</li>\n<li>The setup help text is reworded and no longer references the hidden topic\nfield; the success message now has a &quot;Google Cloud setup complete&quot; title\nand a tick icon.</li>\n<li>The &quot;Clear OAuth client&quot; and per-mailbox &quot;Remove&quot; buttons are grey by\ndefault and red on hover; the &quot;Apply push&quot; button and the help text under\nfields have proper spacing.</li>\n</ul>\n<h2>Technical details</h2>\n<ul>\n<li>New outbound/contact-us target ForwardEmailTarget.ROLE_EMAIL, resolved\nserver-side to the role&#39;s own address without rewriting it\n(server/lib/contact-us/resolve-and-send.ts, committee.model.ts).</li>\n<li>Connected-inbox detection roleForwardsToConnectedInbox() drives the\nContact Us collapse and forces contactUsTarget to ROLE_EMAIL so the saved\nvalue matches the displayed behaviour (committee-member.ts).</li>\n<li>catchAllExists()/catchAllSummary() and catchAllTooltipDestination()\nrecognise Worker catch-alls; the &quot;not yet configured&quot; status is gated to\nthe member-email target only (committee-member.ts, email-routing-status.ts).</li>\n<li>The Active badge tooltip uses roleEmailFor(role) (configured email,\nfalling back to type@domain) for consistency with the routing status\nlogic; removed the unused workerScriptNameFor() method\n(committee-settings.ts).</li>\n<li>Roles table switched to natural (auto) layout with a non-wrapping email\ncell and content-sized status columns (committee-settings.ts).</li>\n<li>Server Cloudflare email-routing endpoints share a single asyncRoute\nwrapper and HttpError type, with a shared pre-flight that creates and\nreports unverified forwarding destinations (server/lib/shared/async-route.ts,\nserver/lib/shared/http-error.ts, cloudflare-email-routing-routes.ts).</li>\n<li>Client error display standardised via extractErrorMessage; verification\n-pending responses shown as an amber notice.</li>\n<li>New migration 20260616000000-update-committee-contact-us-help upserts the\nrefreshed committee-contact-us-help content text so it updates on every\nenvironment at deploy; default-content.ts kept in sync.</li>\n<li>StoredValue keys renamed to match their kebab on-screen values and sorted,\nwith no behaviour change (ui-actions.ts and consumers).</li>\n<li>Gmail inbox settings: Project ID binds to googleInbox.pubsubProjectId so it\npersists on Save; topic-name field removed and defaulted; runSetup reads the\nproject ID from config; statusTitle drives the success alert title + icon\n(system-gmail-inbox-settings.ts).</li>\n<li>New btn-grey-danger style (grey default, red hover) applied to Clear OAuth\nclient and Remove (buttons.sass and the two external-systems components).</li>\n<li>Committee roles delete restricted to non-Contact-Us roles via\nisContactUsSystemRole(); Contact Us shows a disabled delete with tooltip\n(committee-settings.ts).</li>\n</ul>\n"}