The console is a single stateless Node container. State lives in managed services, so it scales to zero and back without losing anything. Notifications run separately as event-driven Cloud Functions.
TLS, CDN, WAF in front of the custom domain
Node + Express, EJS views, Alpine.js islands
Firestore-backed cookie + Bearer JWT for API
Stateless container: any replica can serve any request; nothing is kept in process memory that matters across requests except short-lived caches.
| Service | Role | Why this one |
|---|---|---|
| BigQuery | Analytics warehouse + campaign log + ML | Cheap columnar scans; ARIMA_PLUS for anomalies |
| MongoDB (Atlas) | Campaign audience reads | Read replica avoids the inflated warehouse mirror that would double campaign cost |
| Firestore | Mobile event source of truth | Real-time triggers + authoritative conversion facts |
| Cloud Tasks | Scheduled campaigns | Survives scale-to-zero; fires future jobs reliably |
| Cloud Storage | Creative assets (private) | Signed URLs for external consumers; no public objects |
| Secret Manager | Credentials | No secrets in the image or env literals in source |
| Cloud Functions | Notifications | Independent lifecycle; reacts to Firestore writes |
WhatsApp / Instagram / Messenger webhooks land on the console; chat threads persist to the warehouse.
Cloud Scheduler triggers hourly pulls from Meta Ads, GA4 and Firestore into BigQuery.
Campaigns fan out to the WhatsApp Cloud API; push goes through Cloud Functions to FCM / HMS.