# 03-Jun-2026 — reduce CMS and Gmail runtime pressure [ref #278](https://github.com/nbarrett/ngx-ramblers/issues/278)

## [build 665](https://github.com/nbarrett/ngx-ramblers/actions/runs/26873625529) — [commit 07ff6ca](https://github.com/nbarrett/ngx-ramblers/commit/07ff6ca19feb3fa69155cb5de3b0fc5aa4bcfe2d)

_____

> 📖 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.

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.

Images and caching:
- Expose the aspect-ratio control for real images, applied with object-fit: cover so the setting is non-destructive.
- Stop per-page-load cache-busting of image URLs in card-image so URLs are stable across reloads.
- Add Cache-Control to the S3 image proxy.
- Serve content-hashed static assets, chunks and fonts as immutable.
- Let reloads serve images and chunks from browser or edge cache instead of re-streaming every image through the single Node core.

Canterbury diagnosis:
- 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.
- 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.
- 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.
- 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.

Gmail runtime loading:
- 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.
- 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.
- Add a small inbox runtime helper to check Google Inbox config and enabled mailbox connections without pulling in the full poller stack.
- Start the continuous inbox poller only when Google Inbox OAuth config exists and at least one enabled mailbox connection has an encrypted refresh token.
- 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.

Gmail API footprint:
- 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.
- Keep Google Cloud provisioning on googleapis, but isolate it to the setup-only module and lazy-load that module from the OAuth setup flow.
- 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.
- Defer S3 attachment upload dependencies until an attachment is actually downloaded from Gmail.

Inbox and reply workflow:
- Preserve bodyHtml, bodyText and attachments when inbound and outbound inbox messages are stored, instead of deliberately nulling the message body.
- 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.
- 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.
- 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.
- Add a Back to inbox action for inbox replies, returning to the original conversation via the thread query parameter.
- Let the inbox resolve thread query parameters by either the old subject slug or the stored thread id.
- 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.
- Replace separate draft and sent buttons with a single Show dropdown in the composer toolbar.
- Restyle the Add Cc and Add Bcc toggles as compact quiet buttons to match the surrounding controls.

Fly memory headroom:
- Remove the carried-over --optimize_for_size flag from the web and worker server commands.
- 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.

Expected result:
- Sites without Gmail Inbox configured no longer load the heavy inbox runtime or start the continuous poller as part of normal server startup.
- The Scheduled Tasks UI still shows Gmail jobs and can still pause, trigger or reschedule them via the existing registry/database flow.
- 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.
- Conversation opens avoid repeated Gmail round-trips, and inbox replies have a shorter, reversible user journey.