{"id":"6a1feb5f5a2e00be57ce391e","title":"2026 06 03 Issue 278","path":"how-to/committee/release-notes/2026-06-03-issue-278","contentMarkdown":"# 03-Jun-2026 — reduce CMS and Gmail runtime pressure [ref #278](https://github.com/nbarrett/ngx-ramblers/issues/278)\n\n## [build 665](https://github.com/nbarrett/ngx-ramblers/actions/runs/26873625529) — [commit 07ff6ca](https://github.com/nbarrett/ngx-ramblers/commit/07ff6ca19feb3fa69155cb5de3b0fc5aa4bcfe2d)\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\nScheduled tasks: group the email jobs into Gmail and Brevo sub-tabs on the admin page while keeping Gmail scheduled-task definitions registered at startup so /admin/system-settings?tab=scheduled-tasks remains database-driven and operator-visible.\n\nImages and caching:\n- Expose the aspect-ratio control for real images, applied with object-fit: cover so the setting is non-destructive.\n- Stop per-page-load cache-busting of image URLs in card-image so URLs are stable across reloads.\n- Add Cache-Control to the S3 image proxy.\n- Serve content-hashed static assets, chunks and fonts as immutable.\n- Let reloads serve images and chunks from browser or edge cache instead of re-streaming every image through the single Node core.\n\nCanterbury diagnosis:\n- Canterbury was the outlier on the shared 512MB Fly shape: sibling environments on the same application code stayed healthy, but Canterbury had an observed cgroup OOM exit (137) and was later intermittently unreachable from the environment health page.\n- The deployed image was the same generation as the other sites, so the fix focuses on runtime pressure rather than treating Canterbury as a data-size problem or requiring a 1GB upgrade.\n- Local localhost testing against the same Mongo/S3 resources stayed fast, which pointed away from database or S3 latency as the primary cause and towards Fly instance memory/CPU headroom.\n- The recent Gmail inbox work added googleapis, web-push, Gmail polling, push/digest jobs and inbox routes. Loading that stack eagerly on every site meant environments with no Gmail configuration still paid the startup memory cost.\n\nGmail runtime loading:\n- Lazy-load /api/inbox and /api/inbox/oauth routes so the Gmail route modules, googleapis, web-push and inbox runtime dependencies are only loaded when an inbox route or OAuth flow is actually used.\n- Register inbox-message-digest and inbox-token-health-check as lightweight scheduled tasks, then dynamically import the heavy digest/poller implementations inside the run functions when the jobs actually execute.\n- Add a small inbox runtime helper to check Google Inbox config and enabled mailbox connections without pulling in the full poller stack.\n- Start the continuous inbox poller only when Google Inbox OAuth config exists and at least one enabled mailbox connection has an encrypted refresh token.\n- Let the token-health and digest jobs skip cleanly when Google Inbox OAuth is not configured, rather than importing the poller and then discovering there is nothing to do.\n\nGmail API footprint:\n- Replace the generated googleapis Gmail reader path with a narrow REST client based on google-auth-library OAuth2Client, avoiding the large googleapis import during normal inbox reads, polls and replies.\n- Keep Google Cloud provisioning on googleapis, but isolate it to the setup-only module and lazy-load that module from the OAuth setup flow.\n- Centralise Gmail and Google API roots, scopes, endpoints, labels, headers, MIME values, services and request methods in gmail-inbox.model.ts using enums, constants and interfaces rather than repeated string literals.\n- Defer S3 attachment upload dependencies until an attachment is actually downloaded from Gmail.\n\nInbox and reply workflow:\n- Preserve bodyHtml, bodyText and attachments when inbound and outbound inbox messages are stored, instead of deliberately nulling the message body.\n- Hydrate legacy Gmail messages once on first thread open, persist the fetched body and attachments back to Mongo, and then serve subsequent conversation opens from stored data.\n- Keep the selected conversation responsive while a legacy hydrate is running by switching the inbox detail pane immediately, showing a loading state and ignoring stale slower responses from earlier clicks.\n- Send inbox replies straight to /admin/email-composer?branding=unbranded&tab=compose so users land on the compose step rather than walking through the earlier composer tabs.\n- Add a Back to inbox action for inbox replies, returning to the original conversation via the thread query parameter.\n- Let the inbox resolve thread query parameters by either the old subject slug or the stored thread id.\n- Strip style, script, title, meta, link and head elements before converting quoted HTML to Markdown so Brevo-originated emails do not dump CSS into the reply quote.\n- Replace separate draft and sent buttons with a single Show dropdown in the composer toolbar.\n- Restyle the Add Cc and Add Bcc toggles as compact quiet buttons to match the surrounding controls.\n\nFly memory headroom:\n- Remove the carried-over --optimize_for_size flag from the web and worker server commands.\n- Lower --max_old_space_size from 460MB to 384MB so a 512MB Fly VM keeps room for native allocations, buffers, code space, stacks and GC overhead.\n\nExpected result:\n- Sites without Gmail Inbox configured no longer load the heavy inbox runtime or start the continuous poller as part of normal server startup.\n- The Scheduled Tasks UI still shows Gmail jobs and can still pause, trigger or reschedule them via the existing registry/database flow.\n- Canterbury should have materially more memory headroom on the existing 512MB shape, avoiding the unnecessary 1GB upgrade path unless later evidence shows a separate leak or workload issue.\n- Conversation opens avoid repeated Gmail round-trips, and inbox replies have a shorter, reversible user journey.","contentHtml":"<h1>03-Jun-2026 — reduce CMS and Gmail runtime pressure <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/278\">ref #278</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/26873625529\">build 665</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/07ff6ca19feb3fa69155cb5de3b0fc5aa4bcfe2d\">commit 07ff6ca</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>Scheduled tasks: group the email jobs into Gmail and Brevo sub-tabs on the admin page while keeping Gmail scheduled-task definitions registered at startup so /admin/system-settings?tab=scheduled-tasks remains database-driven and operator-visible.</p>\n<p>Images and caching:</p>\n<ul>\n<li>Expose the aspect-ratio control for real images, applied with object-fit: cover so the setting is non-destructive.</li>\n<li>Stop per-page-load cache-busting of image URLs in card-image so URLs are stable across reloads.</li>\n<li>Add Cache-Control to the S3 image proxy.</li>\n<li>Serve content-hashed static assets, chunks and fonts as immutable.</li>\n<li>Let reloads serve images and chunks from browser or edge cache instead of re-streaming every image through the single Node core.</li>\n</ul>\n<p>Canterbury diagnosis:</p>\n<ul>\n<li>Canterbury was the outlier on the shared 512MB Fly shape: sibling environments on the same application code stayed healthy, but Canterbury had an observed cgroup OOM exit (137) and was later intermittently unreachable from the environment health page.</li>\n<li>The deployed image was the same generation as the other sites, so the fix focuses on runtime pressure rather than treating Canterbury as a data-size problem or requiring a 1GB upgrade.</li>\n<li>Local localhost testing against the same Mongo/S3 resources stayed fast, which pointed away from database or S3 latency as the primary cause and towards Fly instance memory/CPU headroom.</li>\n<li>The recent Gmail inbox work added googleapis, web-push, Gmail polling, push/digest jobs and inbox routes. Loading that stack eagerly on every site meant environments with no Gmail configuration still paid the startup memory cost.</li>\n</ul>\n<p>Gmail runtime loading:</p>\n<ul>\n<li>Lazy-load /api/inbox and /api/inbox/oauth routes so the Gmail route modules, googleapis, web-push and inbox runtime dependencies are only loaded when an inbox route or OAuth flow is actually used.</li>\n<li>Register inbox-message-digest and inbox-token-health-check as lightweight scheduled tasks, then dynamically import the heavy digest/poller implementations inside the run functions when the jobs actually execute.</li>\n<li>Add a small inbox runtime helper to check Google Inbox config and enabled mailbox connections without pulling in the full poller stack.</li>\n<li>Start the continuous inbox poller only when Google Inbox OAuth config exists and at least one enabled mailbox connection has an encrypted refresh token.</li>\n<li>Let the token-health and digest jobs skip cleanly when Google Inbox OAuth is not configured, rather than importing the poller and then discovering there is nothing to do.</li>\n</ul>\n<p>Gmail API footprint:</p>\n<ul>\n<li>Replace the generated googleapis Gmail reader path with a narrow REST client based on google-auth-library OAuth2Client, avoiding the large googleapis import during normal inbox reads, polls and replies.</li>\n<li>Keep Google Cloud provisioning on googleapis, but isolate it to the setup-only module and lazy-load that module from the OAuth setup flow.</li>\n<li>Centralise Gmail and Google API roots, scopes, endpoints, labels, headers, MIME values, services and request methods in gmail-inbox.model.ts using enums, constants and interfaces rather than repeated string literals.</li>\n<li>Defer S3 attachment upload dependencies until an attachment is actually downloaded from Gmail.</li>\n</ul>\n<p>Inbox and reply workflow:</p>\n<ul>\n<li>Preserve bodyHtml, bodyText and attachments when inbound and outbound inbox messages are stored, instead of deliberately nulling the message body.</li>\n<li>Hydrate legacy Gmail messages once on first thread open, persist the fetched body and attachments back to Mongo, and then serve subsequent conversation opens from stored data.</li>\n<li>Keep the selected conversation responsive while a legacy hydrate is running by switching the inbox detail pane immediately, showing a loading state and ignoring stale slower responses from earlier clicks.</li>\n<li>Send inbox replies straight to /admin/email-composer?branding=unbranded&amp;tab=compose so users land on the compose step rather than walking through the earlier composer tabs.</li>\n<li>Add a Back to inbox action for inbox replies, returning to the original conversation via the thread query parameter.</li>\n<li>Let the inbox resolve thread query parameters by either the old subject slug or the stored thread id.</li>\n<li>Strip style, script, title, meta, link and head elements before converting quoted HTML to Markdown so Brevo-originated emails do not dump CSS into the reply quote.</li>\n<li>Replace separate draft and sent buttons with a single Show dropdown in the composer toolbar.</li>\n<li>Restyle the Add Cc and Add Bcc toggles as compact quiet buttons to match the surrounding controls.</li>\n</ul>\n<p>Fly memory headroom:</p>\n<ul>\n<li>Remove the carried-over --optimize_for_size flag from the web and worker server commands.</li>\n<li>Lower --max_old_space_size from 460MB to 384MB so a 512MB Fly VM keeps room for native allocations, buffers, code space, stacks and GC overhead.</li>\n</ul>\n<p>Expected result:</p>\n<ul>\n<li>Sites without Gmail Inbox configured no longer load the heavy inbox runtime or start the continuous poller as part of normal server startup.</li>\n<li>The Scheduled Tasks UI still shows Gmail jobs and can still pause, trigger or reschedule them via the existing registry/database flow.</li>\n<li>Canterbury should have materially more memory headroom on the existing 512MB shape, avoiding the unnecessary 1GB upgrade path unless later evidence shows a separate leak or workload issue.</li>\n<li>Conversation opens avoid repeated Gmail round-trips, and inbox replies have a shorter, reversible user journey.</li>\n</ul>\n"}