# Programmable Messaging API

**Solution Architecture v1.0 · Google Cloud · Integration Platform Architecture · 2026-10 · 22 views · 16 architecture decision records**

Everybody has used this system today without knowing its name. The six-digit code that arrives when you log into your bank, the "your driver is 2 minutes away" text, the prescription reminder, the airline's gate-change message. None were sent by a human, and almost none by the company whose name is on them. They went through a **programmable messaging API** — the Twilio / MessageBird / Vonage / Sinch class: one HTTP endpoint a developer calls with a destination, a body and a sender identity, and a platform behind it that turns that call into a message on a handset anywhere in the world, through carriers it does not own, under regulations it does not write. The product surface is one `POST`. The architecture is hard for exactly one reason, and it is not scale: **the platform cannot see whether the message arrived.** What comes back from a carrier is a receipt that may arrive in two seconds, in four hours, or never; that may say "delivered" when the handset was off; that may arrive twice, out of order, or for a message the platform has no record of. This package is the architecture for a multi-tenant platform that answers those questions honestly: submission and admission, routing across a rented carrier estate, the attempt-and-retry machinery, receipt correlation and state projection, inbound traffic and consent, and per-tenant fairness against capacity somebody else controls. The assumed operating context is 40,000 active tenants across 190 jurisdictions and 9 channels, roughly 220 carrier and aggregator routes with about 6,000 concurrent protocol sessions, 8,000 accepted messages per second with a 5× burst for ten minutes, 25,000 delivery receipts and 2,500 inbound messages per second, and about 20 billion outbound messages a month — on Google Cloud in two regions, with Cloud Spanner for the ledger and consent, Bigtable for the state projection, Pub/Sub for lanes and ingest, Cloud Tasks and Memorystore for per-route pacing, Cloud Run for everything stateless, and a GKE StatefulSet with allowlisted static egress for the carrier binds.

The product is not the message. It is **an accepted message the platform will account for**, and the second half is what rearranges every assumption the first half invites.

The design rests on one boundary: **the accepted message is the unit of durability and the carrier attempt is disposable — delivery state is a monotonic projection over attempts and late-arriving receipts, never a field the submit path writes and never a value a carrier can move backwards.** That single line is why a retry, a park, a re-route, a dead session and a four-hour-late duplicate receipt are one mechanism instead of five, why `unknown` appears in the API without anyone having decided to put it there, and why a correlation bug is correctable by replay rather than permanent.

The decisions that carry the design:

- **The accepted message is owned; the carrier attempt is disposable.** Two replicated writes per attempt buy a system where every carrier failure is the same operation — appending a fact (ADR-01).
- **Delivery state is a projection, not a stored field.** Out-of-order receipts are normal carrier behaviour, not an edge case; a column loses that race silently and a fold cannot (ADR-02).
- **Accept before route.** Submission never waits on a route, a session or a carrier, so the submit SLO does not become the product of 220 third parties' (ADR-03).
- **`unknown` is a first-class terminal state.** A stored status always has a tempting default for "we heard nothing"; a projection has none, so the honesty is structural (ADR-04).
- **The ambiguous submit is resolved by a recorded per-route policy.** No idempotency key means choosing between a duplicate code on a handset and a silent non-delivery, and the right answer differs per route and class (ADR-05).
- **Uncorrelatable receipts are quarantined, never discarded.** A receipt matching nothing is usually the platform's own defect and occasionally the evidence in a billing dispute (ADR-06).
- **Per-route throughput is enforced by leases checked out in blocks.** Breaching a carrier window suspends a sender identity rather than returning a 429, so a stated worst case beats an unbounded approximation (ADR-07).
- **Three traffic classes, separate lanes, a declared shedding order.** Interactive, conversational and bulk are different products sharing only the ledger (ADR-08).
- **Routing decides at attempt time.** A route chosen at submit may be dead when the attempt runs, so the price quoted at submit is labelled an estimate (ADR-09).
- **A thin adapter contract, with per-channel capability declared rather than hidden.** Hiding a difference is taking responsibility for it (ADR-10).
- **Carrier sessions live in a dedicated stateful tier.** Long-lived binds with sequence windows do not belong in a fleet where every scale-in is a teardown and an in-flight ambiguity (ADR-11).
- **The platform owns regulated keywords; the tenant owns marketing preference.** A `STOP` on a shared short code cannot be a tenant preference (ADR-12).
- **Jurisdictional rules are versioned data that fails closed.** A regulator's change must be live in thirty seconds, and data that changes behaviour gets code's review (ADR-13).
- **Receipts and consent outlive the bodies they describe.** Content, the record of acting on it, and the proof of permission are three obligations on three clocks (ADR-14).
- **One rated usage event per attempt.** A fallback hop, a re-route and a multi-segment body each cost money independently (ADR-15).
- **Route eligibility is earned from measured delivery.** Absence of receipts is a signal, not missing data, or a carrier is rewarded for going quiet (ADR-16).

The architecture one-pager (including why the design should still hold up in ten years, what a four-week prototype should falsify, and the six risks that would change it) and the full decision record appear on the landing page of the diagram set, directly below the index of views. The same content is published as [docs/architecture-one-pager.md](docs/architecture-one-pager.md) (~16 min) and [docs/decision-record.md](docs/decision-record.md) (~69 min).

---

## What is here

| Path | Contents |
|---|---|
| `ask.md` | The high-level requirements this architecture answers, in the practice's house format |
| `diagrams/index.html` | The landing page: 22 views in seven acts with every format linked, then the **architecture one-pager** and the **decision record** |
| `diagrams/*.html` | One self-contained page per view: the inlined diagram plus the reasoning cards, with copy / PNG / PDF export |
| `diagrams/svg/*.svg` | The same views as SVG with the diagram XML embedded; they re-open fully editable in diagrams.net |
| `diagrams/drawio/*.drawio` | draw.io source |
| `docs/*.md` | The one-pager and the decision record as markdown, for the site's reading layout |
| `specs/part-*.json` | The view specs, authored in six parts and assembled into `specs/views.json` |
| `specs/manifest-*.json` | Acts, page titles, subtitles and the reasoning cards, assembled into `specs/manifest.json` |
| `specs/adr-*.json` | The one-pager and the sixteen decision records, assembled into `specs/adr.json` |
| `scripts/build.sh` | Rebuilds everything from the specs. Node 20+, no network, no draw.io Desktop |
| `scripts/render-adr.mjs` | Renders `specs/adr.json` into the landing page and into `docs/*.md` |

Edit the `part-*` and `manifest-*` files, never the assembled `views.json`, `manifest.json` or `adr.json`.

## Rebuilding

```bash
cd architecture/deliverables/use-cases/programmable-messaging-api
bash scripts/build.sh
```

The build generates draw.io from the specs, gates it on geometry (`validate.mjs --strict`) and on routing (`route-check.mjs`), renders both SVG flavours, writes the HTML pages and the index, injects the one-pager and the decision record, and proves every relative link resolves. A clean run reports **0 geometry errors, 0 geometry warnings, 0 routing errors and 1 routing warning**, 384 nodes with no unresolved icons, and a self-contained folder.

The one accepted routing warning is on `09-integration`: the `send` label lies across the tenant-callback line. The hub layout gives each spoke its own turning column, so the warning count grows with the number of interfaces drawn — eight interfaces produce four such warnings and four produce one. Four were kept and the two omitted interfaces (the registry API and the rated-usage stream) are named in the diagram note and drawn in views 10, 14 and 18. The alternative was a complete catalogue with four warnings, which reads worse.

## The views

| # | View | Act | What it answers |
|---|---|---|---|
| 01 | System Context | Context and scope | Who calls the platform, who it calls, and which dependencies it cannot control |
| 02 | High-Level Architecture | Context and scope | The seven stages of a message, and the one place durability is bought |
| 03 | Actors and Their Journeys | People and journeys | Six actors and the goal each would state in their own words |
| 04 | Journey — The Login Code | People and journeys | Why the resend, not the wait, is where duplicates are made |
| 05 | Journey — First Send, First Complaint | People and journeys | The developer's trough: an accepted message they cannot account for |
| 06 | Journey — Texting STOP | People and journeys | The only journey whose success condition is that nothing else happens |
| 07 | Layered Architecture | Structure | Eight layers, submit down and receipts up, with one durable write |
| 08 | Container View | Structure | The deployable parts, and the one plane that cannot be stateless |
| 09 | Interface Catalogue | Structure | Four contracts in, four out, and the two somebody else authenticates |
| 10 | Data Flow | Data | One message from submission to evidence, and each store's retention clock |
| 11 | Data Architecture | Data | Four bands by what can be rebuilt, not by what the data is about |
| 12 | Data Model | Data | Message, attempt, receipt, consent — and the nullable key that is the point |
| 13 | Critical Flow — Submit to Carrier Acceptance | Runtime | Which message promises ownership, and which promises nothing |
| 14 | Receipt Ingest and State | Runtime | Verify, acknowledge, correlate, fold — in that order, because carriers do not wait |
| 15 | Per-Channel Pipelines | Runtime | Five lanes behind one contract, and the stages where they genuinely differ |
| 16 | The Ambiguous Submit | Runtime | One attempt, two legitimate endings, and a policy recorded on the message |
| 17 | Deployment | Operations | Two regions, three zones of stateless work, one stateful plane that cannot move |
| 18 | Delivery Pipeline | Operations | Why code, infrastructure and jurisdictional rules ship on three different paths |
| 19 | Observability | Operations | Signal by stage, with one row for the failures that produce no error |
| 20 | Route Lifecycle | Operations | How a carrier route earns traffic, and how it loses it without a ticket |
| 21 | Trust Zones | Assurance | Five zones, and the boundary where a carrier's POST stops being hostile |
| 22 | Identity and Authorisation | Assurance | The four separate proofs required before a message leaves |

## Evidence

This is a design, not a report on a running system. Every rate, latency, ratio, window and retention figure in `ask.md`, in the cards and in the decision records is a **stated assumption**, chosen to be defensible and arguable rather than measured. Where a carrier protocol or a channel's own rules settle a question, the protocol is followed and the records say so rather than claiming credit for it.
