# 11-Feb-2026 — NGX Ramblers Architecture Overview

_____

This article provides a visual tour of the NGX Ramblers platform architecture — every system, service, and integration that makes the application work. Use this as a reference when onboarding, debugging, or planning new features.

## High-Level Architecture

NGX Ramblers is an Angular 21 single-page application backed by a Node.js/Express API, hosted on Fly.io, with MongoDB Atlas as the primary data store. Cloudflare handles DNS, SSL, and CDN — all user traffic passes through it before reaching Fly.io.

```mermaid
flowchart LR
    USER@{ icon: "ngx:user", label: "Users", pos: "b", h: 48 }
    CF@{ icon: "ngx:cloudflare", label: "Cloudflare DNS/CDN", pos: "b", h: 48 }

    USER --> CF

    subgraph fly["Fly.io"]
        FLY@{ icon: "logos:fly-icon", label: "Docker Container", pos: "b", h: 48 }
        ANGULAR@{ icon: "logos:angular-icon", label: "Angular 21 SPA", pos: "b", h: 48 }
        EXPRESS@{ icon: "ngx:express", label: "Express API", pos: "b", h: 48 }
        FLY --> ANGULAR
        ANGULAR --> EXPRESS
        FLY --> EXPRESS
    end

    CF --> FLY
    EXPRESS --> MONGO@{ icon: "ngx:mongodb", label: "MongoDB Atlas", pos: "b", h: 48 }
    EXPRESS --> S3@{ icon: "ngx:aws", label: "AWS S3", pos: "b", h: 48 }
    EXPRESS --> BREVO@{ icon: "ngx:brevo", label: "Brevo", pos: "b", h: 48 }
    EXPRESS --> RAMBLERS@{ icon: "ngx:ramblers-hq", label: "Ramblers API", pos: "b", h: 48 }

    style fly fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
```

## Frontend Stack

The Angular frontend uses a rich set of libraries for maps, charts, galleries, and UI components.

```mermaid
flowchart LR
    SPA@{ icon: "logos:angular-icon", label: "Angular 21", pos: "b", h: 48 }

    subgraph ui["UI & Components"]
        BS@{ icon: "logos:bootstrap", label: "Bootstrap 5", pos: "b", h: 48 }
        FA["FontAwesome"]
        PRIME["PrimeNG"]
    end

    subgraph maps["Maps"]
        LEAF@{ icon: "logos:leaflet", label: "Leaflet", pos: "b", h: 48 }
        GMAP@{ icon: "logos:google-maps", label: "Google Maps", pos: "b", h: 48 }
        OS@{ icon: "ngx:os-maps", label: "OS Maps", pos: "b", h: 48 }
    end

    subgraph media["Media & Content"]
        GALLERY["ng-gallery"]
        CHARTS["Chart.js"]
        MD["ngx-markdown"]
        MERMAID["Mermaid"]
    end

    subgraph analytics["Analytics & Security"]
        GA@{ icon: "logos:google-analytics", label: "Google Analytics", pos: "b", h: 48 }
        RECAP@{ icon: "logos:recaptcha", label: "reCAPTCHA", pos: "b", h: 48 }
    end

    SPA --> ui
    SPA --> maps
    SPA --> media
    SPA --> analytics

    style ui fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style maps fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style media fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style analytics fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
```

## Backend Integrations

The Express API server connects to external services for data storage, email, DNS, event management, and walk data.

```mermaid
flowchart LR
    API@{ icon: "ngx:express", label: "Express API", pos: "b", h: 48 }

    subgraph storage["Storage"]
        MONGO@{ icon: "ngx:mongodb", label: "MongoDB Atlas", pos: "b", h: 48 }
        S3@{ icon: "ngx:aws", label: "AWS S3", pos: "b", h: 48 }
    end

    subgraph email["Email"]
        BREVO@{ icon: "ngx:brevo", label: "Brevo (send)", pos: "b", h: 48 }
        GMAIL@{ icon: "logos:google-gmail", label: "Gmail API (inbox)", pos: "b", h: 48 }
        PUBSUB@{ icon: "logos:google-cloud", label: "Google Cloud Pub/Sub", pos: "b", h: 48 }
        MAILCHIMP@{ icon: "ngx:mailchimp", label: "Mailchimp (legacy)", pos: "b", h: 48 }
    end

    subgraph dns["DNS & Hosting"]
        CF@{ icon: "ngx:cloudflare", label: "Cloudflare", pos: "b", h: 48 }
        FLY@{ icon: "logos:fly-icon", label: "Fly.io", pos: "b", h: 48 }
    end

    subgraph walks["Walks & Events"]
        RAMBLERS@{ icon: "ngx:ramblers-hq", label: "Ramblers API", pos: "b", h: 48 }
        MEETUP@{ icon: "ngx:meetup", label: "Meetup API", pos: "b", h: 48 }
    end

    subgraph geo["Geolocation"]
        GMAP@{ icon: "logos:google-maps", label: "Google Places", pos: "b", h: 48 }
        POSTCODE["Postcode Lookup"]
        OSMAP@{ icon: "ngx:os-maps", label: "OS Maps Proxy", pos: "b", h: 48 }
    end

    API --> storage
    API --> email
    API --> dns
    API --> walks
    API --> geo

    style storage fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style email fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style dns fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style walks fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style geo fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
```

## CI/CD Pipeline

Code flows from GitHub through automated builds and tests, then deploys to Fly.io with secrets imported from a secure store.

```mermaid
flowchart LR
    DEV@{ icon: "logos:github-icon", label: "Push to main", pos: "b", h: 48 }
    GHA@{ icon: "logos:github-actions", label: "GitHub Actions", pos: "b", h: 48 }
    BUILD@{ icon: "logos:docker-icon", label: "Docker Build", pos: "b", h: 48 }
    TEST@{ icon: "logos:typescript-icon", label: "Lint & Test", pos: "b", h: 48 }
    SECRETS@{ icon: "ngx:mongodb", label: "Import Secrets", pos: "b", h: 48 }
    DEPLOY@{ icon: "logos:fly-icon", label: "Deploy to Fly.io", pos: "b", h: 48 }

    DEV --> GHA
    GHA --> TEST
    TEST --> BUILD
    BUILD --> SECRETS
    SECRETS --> DEPLOY
```



## Data Flow: Walk Publishing

When a walk leader creates a walk, data flows through multiple systems to reach all audiences.

```mermaid
flowchart LR
    LEADER@{ icon: "ngx:user", label: "Walk Leader", pos: "b", h: 48 }
    APP@{ icon: "ngx:ramblers", label: "NGX Ramblers", pos: "b", h: 48 }
    DB@{ icon: "ngx:mongodb", label: "MongoDB", pos: "b", h: 48 }
    RAPI@{ icon: "ngx:ramblers-hq", label: "Ramblers API", pos: "b", h: 48 }
    MEETUP@{ icon: "ngx:meetup", label: "Meetup", pos: "b", h: 48 }
    NOTIFY@{ icon: "ngx:brevo", label: "Email Notification", pos: "b", h: 48 }

    LEADER -->|"Create walk"| APP
    APP -->|"Save"| DB
    APP -->|"Upload"| RAPI
    APP -->|"Publish"| MEETUP
    DB -->|"Trigger"| NOTIFY
```

## Email Architecture

NGX Ramblers now handles email in **both directions**. Brevo and Cloudflare cover outbound sending and domain routing, and a built-in **Gmail inbox** brings inbound committee mail into the application itself — with browser push notifications the moment something arrives.

### Outbound: sending

Outbound mail goes through **Brevo**. The unified email composer builds a message — WYSIWYG body, merge fields, recipient selection — and sends it either as a Brevo campaign (bulk) or, increasingly, as a server-side **transactional batch** job. Cloudflare email-routing rules and DNS records (SPF / DKIM / DMARC) keep delivery trustworthy.

```mermaid
flowchart LR
    COMPOSER@{ icon: "ngx:ramblers", label: "Email Composer", pos: "b", h: 48 }
    BREVO@{ icon: "ngx:brevo", label: "Brevo API", pos: "b", h: 48 }
    DELIVER@{ icon: "ngx:user", label: "Recipient Inbox", pos: "b", h: 48 }

    COMPOSER -->|"campaign or transactional batch"| BREVO
    BREVO --> DELIVER

    CF@{ icon: "ngx:cloudflare", label: "Cloudflare", pos: "b", h: 48 }
    CF -->|"DNS: SPF / DKIM / DMARC"| DELIVER
```

### Inbound: the Gmail inbox

Committee role addresses (secretary@, membership@, and so on) are routed by **Cloudflare** into a dedicated **Gmail** mailbox, which NGX reads through the **Gmail API** over OAuth — so committee mail is handled inside the app rather than forwarded to someone's personal mail client. Messages reach the app two ways:

- **Watch mode** — Gmail publishes a change notification to **Google Cloud Pub/Sub**, which pushes it straight to the application. Near real-time.
- **Poll mode** — the application periodically asks Gmail for the history delta since it last looked. A dependable fallback where Pub/Sub is not set up.

Either way, each message is fetched, grouped into a thread, matched to the committee **role** it was addressed to, and stored in MongoDB (`inboxMailboxConnections`, `inboxThreads`, `inboxMessages`). The member assigned to that role reads and replies inside the app; replies go back out through the Brevo transactional path and are written back into the Gmail thread, so the mailbox stays consistent whether it is read in NGX or in Gmail.

```mermaid
flowchart TB
    SENDER@{ icon: "ngx:user", label: "External sender", pos: "b", h: 48 }
    CF@{ icon: "ngx:cloudflare", label: "Cloudflare routing", pos: "b", h: 48 }
    GMAIL@{ icon: "logos:google-gmail", label: "Dedicated Gmail mailbox", pos: "b", h: 48 }
    PUBSUB@{ icon: "logos:google-cloud", label: "Google Cloud Pub/Sub", pos: "b", h: 48 }
    APP@{ icon: "ngx:ramblers", label: "NGX Ramblers", pos: "b", h: 48 }
    DB@{ icon: "ngx:mongodb", label: "threads + messages", pos: "b", h: 48 }
    ROLE@{ icon: "ngx:user", label: "Committee role", pos: "b", h: 48 }

    SENDER -->|"role address"| CF
    CF -->|"forward"| GMAIL
    GMAIL -->|"watch"| PUBSUB
    PUBSUB -->|"push"| APP
    GMAIL -->|"poll: history delta"| APP
    APP -->|"fetch · thread · route"| DB
    DB --> ROLE
    ROLE -->|"reply via Brevo + writeback"| GMAIL
```



### Live notifications

When an inbound message is stored, every member assigned to that role gets a **browser push notification** through a **service worker** and the **Web Push (VAPID)** protocol — no mail client open, no polling. The same signal drives the live unread badge in the admin UI.

```mermaid
flowchart LR
    NEW["New inbound message"]
    APP@{ icon: "ngx:ramblers", label: "NGX Ramblers", pos: "b", h: 48 }
    SW["Service Worker<br/>Web Push · VAPID"]
    MEMBER@{ icon: "ngx:user", label: "Assigned member", pos: "b", h: 48 }

    NEW --> APP
    APP -->|"push to subscribers"| SW
    SW -->|"notification + unread badge"| MEMBER
```



### One inbox, many providers

The reader sits behind a provider interface (`InboxReaderProvider`). Gmail (`gmail-api`) is the implementation running in production today; the same interface leaves room for IMAP and other inbound sources (`brevo-inbound-parse`, `cloudflare-ingress`) without changing how threads, routing or notifications work.

## File & Image Storage

User-uploaded images pass through server-side processing before reaching S3.

```mermaid
flowchart LR
    UPLOAD@{ icon: "logos:angular-icon", label: "Upload from UI", pos: "b", h: 48 }
    PROCESS@{ icon: "logos:nodejs-icon", label: "Resize (Sharp)", pos: "b", h: 48 }
    STORE@{ icon: "ngx:aws", label: "AWS S3", pos: "b", h: 48 }
    SERVE@{ icon: "ngx:user", label: "Serve to browser", pos: "b", h: 48 }

    UPLOAD --> PROCESS --> STORE --> SERVE
```



## Maps & Geolocation

Multiple mapping services work together to provide rich location features.

```mermaid
flowchart TD
    LEAF@{ icon: "logos:leaflet", label: "Leaflet", pos: "b", h: 48 }

    subgraph tiles["Tile Layers"]
        OSM@{ icon: "logos:openstreetmap", label: "OpenStreetMap", pos: "b", h: 48 }
        OSMAP@{ icon: "ngx:os-maps", label: "OS Maps (UK)", pos: "b", h: 48 }
    end

    subgraph search["Location Search"]
        GMAP@{ icon: "logos:google-maps", label: "Google Places", pos: "b", h: 48 }
        POST["Postcode Lookup"]
        ORS["OpenRouteService"]
    end

    LEAF --> tiles
    LEAF --> search

    style tiles fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style search fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
```

## Authentication & Security

The application uses JWT for API authentication, with bcrypt for password storage and reCAPTCHA for public forms.

```mermaid
flowchart LR
    LOGIN@{ icon: "ngx:user", label: "Login", pos: "b", h: 48 }
    AUTH["Passport.js + JWT"]
    HASH["bcrypt"]
    DB@{ icon: "ngx:mongodb", label: "Member Record", pos: "b", h: 48 }
    RECAP@{ icon: "logos:recaptcha", label: "reCAPTCHA v3", pos: "b", h: 48 }
    CONTACT@{ icon: "ngx:user", label: "Contact Form", pos: "b", h: 48 }

    LOGIN --> AUTH --> DB
    AUTH --> HASH
    CONTACT --> RECAP
```



## System Configuration

All external service credentials and feature flags are stored centrally in MongoDB under the `config` collection.

```mermaid
flowchart TD
    CONFIG@{ icon: "ngx:mongodb", label: "Config Collection", pos: "b", h: 48 }

    CONFIG --> K1@{ icon: "ngx:aws", label: "AWS", pos: "b", h: 48 }
    CONFIG --> K2@{ icon: "ngx:cloudflare", label: "Cloudflare", pos: "b", h: 48 }
    CONFIG --> K3@{ icon: "ngx:brevo", label: "Brevo", pos: "b", h: 48 }
    CONFIG --> K4@{ icon: "logos:google-maps", label: "Google Maps", pos: "b", h: 48 }
    CONFIG --> K5@{ icon: "logos:recaptcha", label: "reCAPTCHA", pos: "b", h: 48 }
    CONFIG --> K6@{ icon: "ngx:meetup", label: "Meetup", pos: "b", h: 48 }
    CONFIG --> K7@{ icon: "ngx:ramblers-hq", label: "Ramblers API", pos: "b", h: 48 }
    CONFIG --> K8@{ icon: "logos:google-analytics", label: "Google Analytics", pos: "b", h: 48 }
    CONFIG --> K9@{ icon: "logos:google-gmail", label: "Gmail OAuth (inbox)", pos: "b", h: 48 }
    CONFIG --> K10@{ icon: "logos:google-cloud", label: "Google Cloud Pub/Sub", pos: "b", h: 48 }
    CONFIG --> K11["Web Push (VAPID) keys"]
```

The Web Push (VAPID) keys are explained in [Web Push notifications and VAPID keys](https://ngx-ramblers.org.uk/how-to/technical-articles/2026-02-11-ngx-ramblers-architecture/web-push-vapid).



## Technology Summary

| Layer | Technology | Purpose |
|-------|-----------|---------|
| **Frontend** | Angular 21, Bootstrap 5, PrimeNG | SPA framework, UI components |
| **Backend** | Node.js 24, Express, TypeScript | API server |
| **Database** | MongoDB Atlas, Mongoose | Data persistence |
| **Hosting** | Fly.io, Docker | Container hosting |
| **CDN/DNS** | Cloudflare | DNS, SSL, email routing, CDN |
| **Storage** | AWS S3, Sharp | File storage, image processing |
| **Email (outbound)** | Brevo, Cloudflare | Transactional + bulk send, domain routing, SPF/DKIM/DMARC |
| **Email (inbound)** | Gmail API, Google Cloud Pub/Sub, Web Push (VAPID) | In-app committee inbox, push notifications, live unread badge |
| **Maps** | Leaflet, Google Maps, OS Maps | Map rendering, geocoding |
| **Walks** | Ramblers API, Meetup API | Walk data sync, event publishing |
| **CI/CD** | GitHub Actions, Docker | Build, test, deploy |
| **Analytics** | Google Analytics 4 | User tracking |
| **Security** | JWT, Passport.js, bcrypt, reCAPTCHA | Authentication, bot protection |