# 21-Apr-2026 — Ramblers Team Emails Mock Server

_____

> **Revised 23 July 2026.** This article now describes the mock after its migration from the original NGX API proposal to the published [Ramblers Team Emails 1.0.0 specification](https://app.swaggerhub.com/apis/JAMESKEARS/ramblers-group-email/1.0.0#/). The former Ticket #209 routes, bearer-token authentication and incremental response model are no longer part of the public service.

The [Ramblers Team Emails mock server](https://salesforce-mock.ngx-ramblers.org.uk) is a live development and test implementation of the API published by Ramblers Head Office. It gives NGX Ramblers and other consumers an isolated, repeatable endpoint for building and testing integrations without using real supporter data.

The service is intentionally more capable than SwaggerHub's generated example responses. Operators can create team-specific datasets, generate varied supporter fixtures, issue scoped API keys, import or export Insight Hub spreadsheets and inspect unsubscribe or bounce writebacks.

## Start here

- [Interactive Swagger documentation](https://salesforce-mock.ngx-ramblers.org.uk/docs)
- [Operator console](https://salesforce-mock.ngx-ramblers.org.uk/admin)
- [Raw OpenAPI document](https://salesforce-mock.ngx-ramblers.org.uk/api/openapi.json)
- [Ramblers Team Emails 1.0.0 on SwaggerHub](https://app.swaggerhub.com/apis/JAMESKEARS/ramblers-group-email/1.0.0#/)
- [Mock server source](https://github.com/nbarrett/ramblers-salesforce-mock)
- [Shared contract source](https://github.com/nbarrett/ramblers-salesforce-contract)
- [NGX implementation programme](https://github.com/nbarrett/ngx-ramblers/issues/327)

## Why the mock still matters

Ramblers Head Office owns the published external interface. The mock does not define a competing API and NGX does not treat it as the source of the specification.

Its purpose is to make the published interface practical to consume:

- Consumers can develop before production credentials or representative production data are available.
- Each operator owns isolated team datasets and API keys.
- Synthetic fixtures cover audience and identity combinations that are awkward to reproduce on demand.
- Runtime validation catches request or response drift early.
- Unsubscribe and bounce calls remain visible for inspection.
- The same scenarios can be repeated across NGX, other consumers and future Head Office acceptance testing.

This keeps the contract, mock and consumers separate while ensuring they implement the same interface.

```mermaid
flowchart LR
    Published["Ramblers Team Emails 1.0.0<br/>SwaggerHub"]
    Contract["Shared contract v1.0.2<br/>types + runtime validation"]

    subgraph testService["Live mock service"]
        API["Express API"]
        Admin["Operator console"]
        Atlas@{ icon: "ngx:mongodb", label: "MongoDB Atlas", pos: "b", h: 48 }
        Admin --> Atlas
        API --> Atlas
    end

    NGX@{ icon: "ngx:ramblers", label: "NGX Ramblers", pos: "b", h: 48 }
    Other["Other API consumers"]

    Published --> Contract
    Contract --> API
    Contract --> NGX
    NGX --> API
    Other --> API

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

## The current API

The mock exposes the three operations in Ramblers Team Emails 1.0.0:

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/get_supporters` | Return the current supporter snapshot for a team |
| `POST` | `/unsubscribe` | Record an unsubscribe request for a supporter |
| `POST` | `/bounced_email` | Record a hard or soft email bounce |

Every operation uses the published query credentials:

- `api_key` identifies a valid scoped key.
- `team_code` identifies the team being accessed.

The key and team must match. A key issued for one team cannot read supporters or submit writebacks for another.

The retired routes from the original proposal deliberately return `404`:

- `GET /api/groups/{groupCode}/members`
- `POST /api/members/{membershipNumber}/consent`

There is no compatibility façade for those routes. A consumer using the old interface should fail clearly rather than appear to work against an obsolete contract.

## Supporter snapshots

`GET /get_supporters` returns a complete snapshot rather than an incremental delta. The published response covers:

- members and affiliated members, including the published membership states;
- current volunteers and their role details;
- Wellbeing Walkers;
- supporters with or without a membership number;
- stable `memberRef` and `contactId` identifiers;
- contact and membership information;
- email, post and phone consent;
- walk-programme, campaigning and survey preferences;
- permission flags controlling member, volunteer and Wellbeing Walker email audiences;
- permission to view supporter data.

The mock generates representative combinations of these states. This matters particularly for non-members, because `membershipNo` can be `null` while `memberRef` and `contactId` remain available.

Consumers must not infer a deletion, resignation or transfer merely because a supporter is missing from a later snapshot. The published API does not currently define the meaning of disappearance, so NGX retains the previous record for review.

## Unsubscribe and bounce writebacks

The two write operations use `memberRef` and email address rather than the former membership-number path.

`POST /bounced_email` accepts the published `Hard` and `Soft` values. The mock records the team, supporter, bounce type, received time and outcome so duplicate or repeated provider events can be inspected.

`POST /unsubscribe` is also recorded, but the mock deliberately does not invent the resulting consent change. Ramblers Head Office still needs to confirm:

- which preference the operation changes;
- whether the effect applies only to the supplied team or across Ramblers;
- whether `memberRef` is definitively the writeback identifier in every case.

Until those semantics are confirmed, an accepted mock unsubscribe means that the request was structurally valid and auditable, not that a locally invented consent rule has been applied.

## Shared contract and drift detection

The mock and NGX consume [`@ramblers/sf-contract` v1.0.2](https://github.com/nbarrett/ramblers-salesforce-contract/releases/tag/v1.0.2).

The package contains:

- TypeScript request, response and supporter types;
- Zod validators used at runtime;
- the resolved Ramblers Team Emails 1.0.0 OpenAPI definition;
- the generated OpenAPI artefacts used by the mock;
- error and status mappings;
- ESM and CommonJS entry points;
- a migration guide from the original #209 wire format.

The contract repository checks the live resolved SwaggerHub definition against its pinned copy. A material upstream change produces a reviewable failure rather than silently changing consumer behaviour. Formatting-only differences are normalised away.

```mermaid
flowchart LR
    Swagger["SwaggerHub<br/>resolved OpenAPI"]
    Pinned["Pinned upstream definition"]
    Schemas["Types + Zod schemas"]
    Mock["Mock build and tests"]
    NGX["NGX build and tests"]

    Swagger --> Drift{"Semantic drift check"}
    Pinned --> Drift
    Drift --> Schemas
    Schemas --> Mock
    Schemas --> NGX
```

## Operator and team isolation

The operator console is separate from public API authentication.

An operator signs in, creates or selects a team and can then:

- import an Insight Hub `ExportAll.xlsx`;
- generate synthetic supporter fixtures;
- preview the team's current supporters;
- issue or revoke team-scoped API keys;
- inspect unsubscribe and bounce writebacks;
- clear and rebuild test data.

API keys are shown once and stored only as hashes plus non-secret prefixes. Every supporter, key and writeback record carries the owning team code, and all data access is filtered by it.

The root operator can administer the service. Ordinary operators only see teams they own.

## Typical test flow

1. Sign in to the [operator console](https://salesforce-mock.ngx-ramblers.org.uk/admin).
2. Create or select a team using the intended `team_code`.
3. Import Insight Hub data or generate synthetic supporters.
4. Generate an API key for that team and copy it when shown.
5. Open the [Swagger UI](https://salesforce-mock.ngx-ramblers.org.uk/docs).
6. Call `GET /get_supporters` with the key and matching team code.
7. Exercise hard and soft bounce calls or an unsubscribe request.
8. Return to the operator console and inspect the writeback history.
9. Try the same key with a different team code to confirm isolation.

## Infrastructure and deployment

The service remains deliberately small:

- Node.js and TypeScript;
- Express for the HTTP API;
- Mongoose and MongoDB Atlas for isolated test data;
- Zod and the shared contract for runtime validation;
- `swagger-ui-express` for interactive documentation;
- a lightweight operator interface compiled with Vite;
- Fly.io hosting behind the `salesforce-mock.ngx-ramblers.org.uk` hostname.

Pushes to `main` run typechecking, linting, build and tests before deployment. The latest implementation includes route tests for valid and invalid credentials, cross-team attempts, published response shapes, hard and soft bounce values and rejection of the retired routes.

## Insight Hub during transition

Insight Hub import and export remain useful test facilities because groups still depend on spreadsheets for supporter fields not included in API 1.0.0.

That compatibility should not be mistaken for the desired end state. The missing supporter-field set and the operational and GDPR implications of continuing to handle exports are tracked in [NGX Ticket #334](https://github.com/nbarrett/ngx-ramblers/issues/334).

## Known Phase One questions

The mock follows the published document without answering questions that belong to Ramblers Head Office:

- whether area codes are valid `team_code` values;
- whether area-sized responses will be paged;
- the exact scope and effect of `/unsubscribe`;
- whether `memberRef` is the definitive writeback identifier;
- the intended distinction between `membershipExpiry` and `membershipEndDate`;
- the meaning of `canViewMemberDate`;
- how consumers should interpret a supporter disappearing from a snapshot.

These are visible constraints rather than hidden assumptions.

## Current implementation tickets

- [ramblers-salesforce-contract#2](https://github.com/nbarrett/ramblers-salesforce-contract/issues/2) — shared contract alignment, completed in v1.0.2.
- [ramblers-salesforce-contract#3](https://github.com/nbarrett/ramblers-salesforce-contract/issues/3) — published-interface drift detection.
- [ramblers-salesforce-mock#9](https://github.com/nbarrett/ramblers-salesforce-mock/issues/9) — the three Ramblers Team Emails operations.
- [ramblers-salesforce-mock#10](https://github.com/nbarrett/ramblers-salesforce-mock/issues/10) — supporter fixtures and writeback auditing.
- [ngx-ramblers#327](https://github.com/nbarrett/ngx-ramblers/issues/327) — ordered NGX adoption programme.
- [ngx-ramblers#334](https://github.com/nbarrett/ngx-ramblers/issues/334) — deferred supporter-field parity and Insight Hub retirement.

## References

- [Ramblers Team Emails 1.0.0](https://app.swaggerhub.com/apis/JAMESKEARS/ramblers-group-email/1.0.0#/)
- [Live mock](https://salesforce-mock.ngx-ramblers.org.uk)
- [Interactive Swagger UI](https://salesforce-mock.ngx-ramblers.org.uk/docs)
- [Operator console](https://salesforce-mock.ngx-ramblers.org.uk/admin)
- [Raw OpenAPI JSON](https://salesforce-mock.ngx-ramblers.org.uk/api/openapi.json)
- [Mock repository](https://github.com/nbarrett/ramblers-salesforce-mock)
- [Shared contract repository](https://github.com/nbarrett/ramblers-salesforce-contract)
- [NGX Ramblers repository](https://github.com/nbarrett/ngx-ramblers)

Questions or suggested test scenarios can be raised through the [mock repository](https://github.com/nbarrett/ramblers-salesforce-mock/issues) or sent through [NGX support](https://ngx-ramblers.org.uk/?contact-us&role=support&subject=Ramblers%20Team%20Emails%20Mock&redirect=how-to/technical-articles/2026-04-21-ramblers-salesforce-mock-server).
