21-Jun-2026 — recipient-only mail routing, per-role notifications via the main save, live unread badg... #293
build 710 — commit d77a308
Several fixes and simplifications to the Gmail inbox, found while testing the Pub/Sub setup and per-role notifications on staging.
Inbound mail routing (bug fix)
Incoming messages were filed under every committee role whose known addresses matched any address on the message, including the sender. A message sent from an address that appears in another role's Inbound Forwarding recipients was therefore filed under that role as well, producing duplicate conversations: a test sent From nick.barrett36@gmail.com To nick.barrett@ngx-ramblers.org.uk landed under both support-representative (correct, its role address) and info (wrong, because the sending address is in info's forwarding recipients list).
Routing now matches on the recipients only (To/Cc), never the sender, for both the inbox and spam paths, so a message is filed only under the role(s) it was actually addressed to. New helper messageRecipientEmails; processGmailMessageIds and pollSpamForConnection use it in place of messageAddressEmails.
Per-role notifications use the page's main Save / Undo, saved atomically
The role-mailboxes step had its own "Save notification settings" button that wrote immediately via dedicated endpoints. It now defers to the system settings page's existing Save / Save settings and exit / Undo Changes, following the same membersPendingSave pattern already used for member edits:
- The component emits its pending changes (per role: no notification, notify the assigned member, or notify a different address) via a pendingChanges output; it no longer saves directly and has no own button.
- system-settings collects them (inboxRoleNotificationsPendingSave) and persists the whole batch in save() via a single atomic write: PUT /aliases/notifications applies every change to the committee config in one read-modify-write. The earlier approach wrote each role separately and concurrently, so the writes clobbered each other (lost update) and not every change stuck.
- Undo Changes / Exit Without Saving discard the pending edits and reload the component via an incrementing refresh token (bubbled through system-gmail-inbox-settings). New InboxRoleNotificationSetting model type carries each pending change.
Header unread badge updates live
The header inbox badge subscribed to the new-message and thread-updated websocket events but trusted the count carried in the broadcast, which was computed inconsistently (global unread for new mail, per-member for reads) and so was never right for every connected member; it also ignored events for a role that currently showed zero unread. The badge now re-fetches this member's own scoped counts (debounced) whenever a relevant inbox event arrives, so it reflects new mail (including a role going from zero to one) and the member's own read state without a full page refresh.
Google Cloud setup is tolerant of a missing Resource Manager API, and quieter
- The "verify project" step is no longer fatal. When Cloud Resource Manager cannot resolve the project name (commonly because that API is not enabled on the project), it records a skipped result and continues with the project number, which Pub/Sub and Service Usage accept directly, rather than aborting the whole setup.
- The setup result is transient run feedback instead of persisted state shown on every page load. It appears only while a run is in progress or immediately after one (setupStarted is then stripped from the URL so a refresh does not re-show it), collapses to the "complete" banner on success (the per-step list, including the skipped verify, is hidden), and shows only the failed step on failure. The summary banner no longer duplicates a step that is already listed below.
Push delivery applies on selection
Choosing "Push - real-time via Pub/Sub" on a connected Gmail now applies the watch automatically using the topic from the Google Cloud setup. The manual Pub/Sub topic field and the separate "Apply push" button are removed, replaced by a one-line read-only confirmation of the topic in use. If no topic is set up yet, delivery stays on polling with a prompt to run the Google Cloud setup first. Dropped the now-dead pushUrl field and an unused logger.