{"id":"6a37e3ed2fc0d152b3d12685","title":"2026 06 21","path":"how-to/committee/release-notes/2026-06-21","contentMarkdown":"# 21-Jun-2026 — recipient-only mail routing, per-role notifications via the main save, live unread badg... [#293](https://github.com/nbarrett/ngx-ramblers/issues/293)\n\n## [build 710](https://github.com/nbarrett/ngx-ramblers/actions/runs/27905275631) — [commit d77a308](https://github.com/nbarrett/ngx-ramblers/commit/d77a3088b04cd34d65534fc3da4898107bc95ab1)\n\n_____\n\nSeveral fixes and simplifications to the Gmail inbox, found while testing the\nPub/Sub setup and per-role notifications on staging.\n\n## Inbound mail routing (bug fix)\n\nIncoming messages were filed under every committee role whose known addresses\nmatched any address on the message, including the sender. A message sent from\nan address that appears in another role's Inbound Forwarding recipients was\ntherefore filed under that role as well, producing duplicate conversations: a\ntest sent From nick.barrett36@gmail.com To nick.barrett@ngx-ramblers.org.uk\nlanded under both support-representative (correct, its role address) and info\n(wrong, because the sending address is in info's forwarding recipients list).\n\nRouting now matches on the recipients only (To/Cc), never the sender, for both\nthe inbox and spam paths, so a message is filed only under the role(s) it was\nactually addressed to. New helper messageRecipientEmails; processGmailMessageIds\nand pollSpamForConnection use it in place of messageAddressEmails.\n\n## Per-role notifications use the page's main Save / Undo, saved atomically\n\nThe role-mailboxes step had its own \"Save notification settings\" button that\nwrote immediately via dedicated endpoints. It now defers to the system settings\npage's existing Save / Save settings and exit / Undo Changes, following the\nsame membersPendingSave pattern already used for member edits:\n\n- The component emits its pending changes (per role: no notification, notify the\nassigned member, or notify a different address) via a pendingChanges output;\nit no longer saves directly and has no own button.\n- system-settings collects them (inboxRoleNotificationsPendingSave) and persists\nthe whole batch in save() via a single atomic write: PUT /aliases/notifications\napplies every change to the committee config in one read-modify-write. The\nearlier approach wrote each role separately and concurrently, so the writes\nclobbered each other (lost update) and not every change stuck.\n- Undo Changes / Exit Without Saving discard the pending edits and reload the\ncomponent via an incrementing refresh token (bubbled through\nsystem-gmail-inbox-settings). New InboxRoleNotificationSetting model type\ncarries each pending change.\n\n## Header unread badge updates live\n\nThe header inbox badge subscribed to the new-message and thread-updated\nwebsocket events but trusted the count carried in the broadcast, which was\ncomputed inconsistently (global unread for new mail, per-member for reads) and\nso was never right for every connected member; it also ignored events for a role\nthat currently showed zero unread. The badge now re-fetches this member's own\nscoped counts (debounced) whenever a relevant inbox event arrives, so it\nreflects new mail (including a role going from zero to one) and the member's own\nread state without a full page refresh.\n\n## Google Cloud setup is tolerant of a missing Resource Manager API, and quieter\n\n- The \"verify project\" step is no longer fatal. When Cloud Resource Manager\ncannot resolve the project name (commonly because that API is not enabled on\nthe project), it records a skipped result and continues with the project\nnumber, which Pub/Sub and Service Usage accept directly, rather than aborting\nthe whole setup.\n- The setup result is transient run feedback instead of persisted state shown on\nevery page load. It appears only while a run is in progress or immediately\nafter one (setupStarted is then stripped from the URL so a refresh does not\nre-show it), collapses to the \"complete\" banner on success (the per-step list,\nincluding the skipped verify, is hidden), and shows only the failed step on\nfailure. The summary banner no longer duplicates a step that is already listed\nbelow.\n\n## Push delivery applies on selection\n\nChoosing \"Push - real-time via Pub/Sub\" on a connected Gmail now applies the\nwatch automatically using the topic from the Google Cloud setup. The manual\nPub/Sub topic field and the separate \"Apply push\" button are removed, replaced\nby a one-line read-only confirmation of the topic in use. If no topic is set up\nyet, delivery stays on polling with a prompt to run the Google Cloud setup\nfirst. Dropped the now-dead pushUrl field and an unused logger.","contentHtml":"<h1>21-Jun-2026 — recipient-only mail routing, per-role notifications via the main save, live unread badg... <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/293\">#293</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/27905275631\">build 710</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/d77a3088b04cd34d65534fc3da4898107bc95ab1\">commit d77a308</a></h2>\n<hr>\n<p>Several fixes and simplifications to the Gmail inbox, found while testing the\nPub/Sub setup and per-role notifications on staging.</p>\n<h2>Inbound mail routing (bug fix)</h2>\n<p>Incoming messages were filed under every committee role whose known addresses\nmatched any address on the message, including the sender. A message sent from\nan address that appears in another role&#39;s Inbound Forwarding recipients was\ntherefore filed under that role as well, producing duplicate conversations: a\ntest sent From <a href=\"mailto:nick.barrett36@gmail.com\">nick.barrett36@gmail.com</a> To <a href=\"mailto:nick.barrett@ngx-ramblers.org.uk\">nick.barrett@ngx-ramblers.org.uk</a>\nlanded under both support-representative (correct, its role address) and info\n(wrong, because the sending address is in info&#39;s forwarding recipients list).</p>\n<p>Routing now matches on the recipients only (To/Cc), never the sender, for both\nthe inbox and spam paths, so a message is filed only under the role(s) it was\nactually addressed to. New helper messageRecipientEmails; processGmailMessageIds\nand pollSpamForConnection use it in place of messageAddressEmails.</p>\n<h2>Per-role notifications use the page&#39;s main Save / Undo, saved atomically</h2>\n<p>The role-mailboxes step had its own &quot;Save notification settings&quot; button that\nwrote immediately via dedicated endpoints. It now defers to the system settings\npage&#39;s existing Save / Save settings and exit / Undo Changes, following the\nsame membersPendingSave pattern already used for member edits:</p>\n<ul>\n<li>The component emits its pending changes (per role: no notification, notify the\nassigned member, or notify a different address) via a pendingChanges output;\nit no longer saves directly and has no own button.</li>\n<li>system-settings collects them (inboxRoleNotificationsPendingSave) and persists\nthe whole batch in save() via a single atomic write: PUT /aliases/notifications\napplies every change to the committee config in one read-modify-write. The\nearlier approach wrote each role separately and concurrently, so the writes\nclobbered each other (lost update) and not every change stuck.</li>\n<li>Undo Changes / Exit Without Saving discard the pending edits and reload the\ncomponent via an incrementing refresh token (bubbled through\nsystem-gmail-inbox-settings). New InboxRoleNotificationSetting model type\ncarries each pending change.</li>\n</ul>\n<h2>Header unread badge updates live</h2>\n<p>The header inbox badge subscribed to the new-message and thread-updated\nwebsocket events but trusted the count carried in the broadcast, which was\ncomputed inconsistently (global unread for new mail, per-member for reads) and\nso was never right for every connected member; it also ignored events for a role\nthat currently showed zero unread. The badge now re-fetches this member&#39;s own\nscoped counts (debounced) whenever a relevant inbox event arrives, so it\nreflects new mail (including a role going from zero to one) and the member&#39;s own\nread state without a full page refresh.</p>\n<h2>Google Cloud setup is tolerant of a missing Resource Manager API, and quieter</h2>\n<ul>\n<li>The &quot;verify project&quot; step is no longer fatal. When Cloud Resource Manager\ncannot resolve the project name (commonly because that API is not enabled on\nthe project), it records a skipped result and continues with the project\nnumber, which Pub/Sub and Service Usage accept directly, rather than aborting\nthe whole setup.</li>\n<li>The setup result is transient run feedback instead of persisted state shown on\nevery page load. It appears only while a run is in progress or immediately\nafter one (setupStarted is then stripped from the URL so a refresh does not\nre-show it), collapses to the &quot;complete&quot; banner on success (the per-step list,\nincluding the skipped verify, is hidden), and shows only the failed step on\nfailure. The summary banner no longer duplicates a step that is already listed\nbelow.</li>\n</ul>\n<h2>Push delivery applies on selection</h2>\n<p>Choosing &quot;Push - real-time via Pub/Sub&quot; on a connected Gmail now applies the\nwatch automatically using the topic from the Google Cloud setup. The manual\nPub/Sub topic field and the separate &quot;Apply push&quot; button are removed, replaced\nby a one-line read-only confirmation of the topic in use. If no topic is set up\nyet, delivery stays on polling with a prompt to run the Google Cloud setup\nfirst. Dropped the now-dead pushUrl field and an unused logger.</p>\n"}