01The accepted message is owned; the carrier attempt is disposable
Two durable writes per attempt buy a system where every carrier failure is the same mechanism: appending a fact. The alternative pays by re-running what already succeeded.
ADR-0122 views, in reading order. Every view ships three ways: an HTML page, an SVG that re-opens in diagrams.net fully editable, and draw.io source.
Twenty-two views of the platform behind every login code and delivery notification: one HTTP call in, a message on a handset out, through carriers the platform does not own and cannot see inside. Read in order — the boundary first, then the people it happens to, then the structure that keeps an accepted message honest when the carrier goes quiet. Every number in the cards is a stated assumption; the requirement document names them as such.
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.
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: 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. It hands the message to a carrier or an aggregator, and what comes back 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. Everything difficult here follows from that: what the acknowledgement at submit is allowed to promise, what happens when a submit times out with no answer, whether a status field or a fold over facts decides the truth, how much carrier capacity may be spent before a sender identity is suspended, and who is accountable when somebody texts STOP to a number four tenants share.
The design rests on one boundary. An accepted message is owned: it is validated, screened against consent and jurisdictional rules, and appended to a strongly consistent ledger before the API acknowledges anything, and the acknowledgement promises ownership rather than delivery. Everything after that point is disposable. A carrier attempt is a separate append-only record with its own route, segments, price and outcome, so a 503 is another attempt, a 429 is a park with a release time, a dead session is a resume, and a worker's death costs nothing. Delivery state is never written by the submit path and never stored as a mutable column: it is a fold over the attempt series and the receipts correlated to it, which is what makes a receipt arriving four hours late, twice, out of order, or after the body has been deleted a fact to append rather than a race to lose. Because the fold over "one attempt accepted, no receipt, deadline passed" is unknown, the platform's most unwelcome product requirement becomes structurally unavoidable rather than a matter of downstream discipline. Around that spine sit four things the platform does not control and must therefore model explicitly: carrier throughput windows, enforced by per-route leases because breaching one suspends a sender identity rather than merely rate-limiting it; traffic classes, because an OTP and a four-million-message campaign are different products sharing only the ledger; channel adapters, so that adding RCS is an adapter and a route family rather than a change to the state model; and a stateful session tier for long-lived carrier binds, which fit neither the elastic fleet above them nor the carriers below.
Two durable writes per attempt buy a system where every carrier failure is the same mechanism: appending a fact. The alternative pays by re-running what already succeeded.
ADR-01Out-of-order receipts are the normal behaviour of carrier networks, not an edge case. A column loses that race silently; a fold cannot.
ADR-02Submission never waits on a route, a session or a carrier, so the two fail separately and the submit SLO is achievable without any of them being healthy.
ADR-03A stored status always has a tempting default for "we heard nothing". A projection has none, so the honesty is enforced by structure rather than by discipline.
ADR-04No idempotency key means choosing between a duplicate code on a handset and a silent non-delivery. The choice differs by route and class, and is written onto the message.
ADR-05A receipt matching nothing is usually a platform bug and occasionally the evidence in a billing dispute. Dropping it destroys both.
ADR-06A carrier window is contractual: breaching it suspends a sender identity. Correctness here is worth a bounded overshoot and a lease that can be stranded.
ADR-07Interactive, conversational and bulk get separate lanes and separate SLOs, and load is shed by refusing in a stated order rather than by making everything slower.
ADR-08A route chosen at submit may be dead when the attempt runs. The price quoted at submit becomes an estimate, and that is said out loud in the API.
ADR-09Channels differ in ways tenants need to see. Hiding the differences makes the platform responsible for a translation nobody asked for.
ADR-10Long-lived binds with sequence windows and allowlisted egress do not belong in an elastic fleet where every scale-in is a teardown and an in-flight ambiguity.
ADR-11STOP on a shared number cannot be a tenant preference. The platform actions it, then tells the tenant it has already been actioned.
ADR-12A rule change must not need a deployment, and a message the platform is unsure it may send is not sent. Everything else degrades.
ADR-13Content retention is a privacy decision; receipt retention is an evidence decision. Collapsing them means disputes arrive after the proof has gone.
ADR-14A fallback hop, a re-route and a multi-segment body each cost money independently, so the message is the wrong unit to rate.
ADR-15Contracted quality is a sales document. A route with no measured outcome cannot stay eligible, because absence of receipts is itself a signal.
ADR-16SMPP, the aggregator of the month, Spanner, Bigtable, the channel that replaces RCS and probably the regulator's current rulebook will all be replaced inside this platform's life. These five properties should survive the replacement, and each is a statement about where something lives rather than about what it is built from.
Any system whose outcome is reported asynchronously by a party it does not control faces the same race, and the race gets worse as the number of reporting parties grows. A projection over append-only facts is indifferent to arrival order, which is the one property that does not degrade with scale, with a new channel, or with a carrier whose receipts are worse than the last one's.
What the platform promised a caller and what it tried on their behalf are different lifetimes with different owners. Keeping them separate is why a retry, a park, a re-route and a worker death are one mechanism rather than four special cases, and it will still be why when the protocol underneath has changed twice.
Naming the unknown rather than rounding it away is a design stance, not a feature. It survives because the alternative compounds: once a status field has been allowed a comfortable default, every downstream report, SLO and routing signal inherits the lie, and no later component can recover the distinction.
The platform will always be spending throughput it rents under terms it did not set. Modelling that as a lease with a release time, rather than as an error to retry, keeps pacing a normal state and keeps the platform's relationship with its suppliers intact — whoever the suppliers are by then.
The retention of content, of the record of what was done with it, and of the proof of permission are three different obligations with three different time horizons and three different regulators. A system that gives them one policy will be wrong about at least two, and the mistake is only discovered when somebody asks.
Every target below is a stated assumption, sized for the operating context in the evidence note and chosen so a reviewer can argue with a number rather than with a sentiment. Each names the view where the mechanism that delivers it is drawn.
| Quality | Target | How it is met | View |
|---|---|---|---|
| Submit availability | ≥ 99.99% monthly | Stateless submit across zones, one durable ledger write, no dependency on any route, session or carrier being healthy | 13 |
| Receipt and inbound ingest availability | ≥ 99.99% monthly | Verify, make durable, acknowledge — correlation happens afterwards, so ingest never waits on the ledger being queryable | 14 |
| Consent evaluation availability | ≥ 99.99% monthly | Strongly consistent store read on every submit, cached with a bounded staleness ceiling, failing closed past it | 21 |
| Submit acknowledgement latency | p95 ≤ 150 ms, p99 ≤ 400 ms | One synchronous ledger commit; validation, sender and consent checks are local reads or cached reads | 13 |
| Interactive time to carrier acceptance | p50 ≤ 700 ms, p95 ≤ 2 s | Dedicated interactive lane, per-route lease taken locally from a checked-out block, no cross-region hop | 15 |
| Receipt visibility | p95 ≤ 2 s from carrier receipt | Stream-driven correlation and fold, with the projection written ahead of the callback dispatch | 14 |
| Throughput | 8,000 msg/s steady, 40,000/s for 10 min | Independent scaling of submit, scheduling, attempt and ingest; ledger partitioned by tenant and time | 17 |
| Durability of accepted messages | RPO 0, zero lost | Synchronously replicated ledger; the acknowledgement is issued only after commit | 11 |
| Projection recovery | RTO 2 h to rebuild | State is a fold, so the projection is disposable; status queries degrade rather than fail during a rebuild | 10 |
| Duplicate handset deliveries | ≤ 1 per 10,000,000 accepted | Client-reference idempotency at submit, a single attempt key per carrier call, and a default of not re-attempting an ambiguous submit | 16 |
| Consent correctness | zero sends after withdrawal, outside 30 s | Inbound keyword interception writes consent before the tenant is notified; propagation bound is stated, not implied | 06 |
| Fairness | interactive queue age p99 ≤ 3 s | Class lanes first, then per-tenant share capped at 40% of a shared route while others have work queued | 19 |
| Cost | platform ≤ 8% of carrier segment cost | Ledger write path held to ≤ 35% of platform cost; dormant tenants and idle routes near zero | 19 |
The prototype's job is to falsify the three claims the design rests on: that a fold over attempts and receipts is cheap enough to answer status queries at rate, that per-route lease enforcement holds a contractual window across an elastic fleet without a cross-region hop, and that the ambiguous-submit default produces fewer bad outcomes than re-attempting. Everything else can be adjusted; if any of those three is wrong, the boundary in ADR-01 has to move.
| Risk | If it lands | Response |
|---|---|---|
| The ledger write path is the platform's cost and scale ceiling | Two synchronously replicated writes per attempt at 8,000 messages a second with retries and fallbacks is the hottest path in the system; if it exceeds its stated share of per-message cost the design is uneconomic rather than merely expensive | Price it explicitly as a reported metric per message rather than per month, hold it to the 35% share in the NFRs, and treat batching of intent writes as the first optimisation to evaluate — never by weakening the intent-before-call ordering |
| The ambiguous-submit default is a judgement a real customer may reject | Not re-attempting protects a recipient from a duplicate code and accepts a silent non-delivery, which for a bank's login traffic may be exactly the wrong trade | Keep it a per-route and per-class policy with the choice recorded on each message, publish the measured duplicate and unknown rates per route, and let a tenant's traffic class select the other default where they can defend it |
| Unknown-state share will be read as a platform defect | Routes that never report receipts inflate the unknown rate for reasons outside the platform, and the honest number is a worse-looking number than a competitor's rounded one | Report unknown share per route alongside the carrier's own reporting behaviour, make it a routing input so bad reporters lose traffic, and state the distinction in the product rather than in a support article |
| Carrier egress allowlists make regional expansion a correspondence exercise | Standby binds in a second region need their source addresses approved by every carrier before failover works, on the carrier's timetable, not the platform's | Treat the allowlist as a tracked dependency per route with its own state, refuse to count a region as a failover target until binds there have carried real traffic, and rehearse on a schedule rather than on an incident |
| Consent on shared sender identities is a liability concentrator | One STOP on a number four tenants share must silence all four, and the platform — not the tenant — will be asked why it did not | Keep regulated keywords platform-owned and the consent history append-only and queryable per destination, publish the 30 s propagation bound as a commitment, and make in-flight messages after a withdrawal a reported number rather than an unexamined gap |
| The quarantine of uncorrelatable receipts becomes a queue nobody works | A rising quarantine rate is the earliest signal of a correlation defect and the easiest dashboard to stop reading, and the window in which a billing dispute can be answered closes quietly | Give the quarantine an owner and an age distribution rather than a count, alert on the rate of change rather than the level, and require a named cause before any quarantined receipt ages out |
Sixteen decisions, grouped by the question they answer, each with the forcing question, the alternatives, what would flip the choice, and why it should outlast the technology it is realised on.
Status of this document. This is a design, not a report on a running system. Every rate, latency, ratio, window and retention figure is a stated assumption, chosen to be defensible and arguable rather than measured. The operating context assumed throughout is a multi-tenant messaging platform serving 40,000 active tenants across 190 jurisdictions and 9 channels, with roughly 220 carrier and aggregator routes and 6,000 concurrent protocol sessions, accepting 8,000 messages per second at steady state with a 5x burst for 10 minutes, ingesting 25,000 delivery receipts and 2,500 inbound messages per second, and carrying about 20 billion outbound messages a month on Google Cloud in two regions. 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; every record here is about something the protocols deliberately leave to the platform.
The decision that defines the architecture, and the two that follow from it directly.
ADR-01The accepted message is the unit of durability; the carrier attempt is disposable ADR-02Delivery state is a monotonic projection over attempts and receipts, not a stored field ADR-03Accept before route: submission never waits on a route, a session or a carrierWhat the platform does when the carrier is late, silent, contradictory, or talking about a message nobody recognises.
ADR-04Unknown is a first-class terminal state, reported as such ADR-05The ambiguous submit is resolved by a recorded per-route policy, defaulting to no re-attempt ADR-06Receipts that correlate to nothing are quarantined, never discardedSpending somebody else's throughput without losing the right to spend it, and deciding whose traffic waits.
ADR-07Per-route throughput is enforced by leases checked out in blocks, not by a central broker or local guesswork ADR-08Three traffic classes with separate lanes, separate SLOs and a declared shedding order ADR-09Routing decides at attempt time, which makes the price quoted at submit an estimateHow much of a channel's strangeness is hidden, and where the one stateful thing in the platform lives.
ADR-10A thin adapter contract, with per-channel capability declared rather than hidden ADR-11Carrier protocol sessions live in a dedicated stateful tier, not in the attempt fleetThe rules the platform will be fined for, and the records that answer for them years later.
ADR-12The platform owns regulated keywords; the tenant owns marketing preference ADR-13Jurisdictional rules are versioned data that fails closed, and changes without a deployment ADR-14Receipts and consent outlive the bodies they describe, and store-no-body is a supported modeWhat a message actually costs, and how a route earns the right to carry one.
ADR-15One rated usage event per attempt, not per message ADR-16Route eligibility is earned from measured delivery, and absence of receipts is a signalEvery component on the twenty-two views, why it was chosen on Google Cloud, what would have been used instead, and the decision record that explains the trade. The topic belongs to no cloud; Google Cloud was chosen as the practice's honest rotation, and the requirement document stays vendor-neutral throughout.
| Capability | Choice | Origin | Credible alternative | Why this one | Record |
|---|---|---|---|---|---|
| Message ledger: append-only submissions and attempts, strongly consistent, RPO 0 | Cloud Spanner, multi-region instance, interleaved attempt rows under the message, partitioned by tenant and time | Google Cloud managed | A globally distributed SQL database with per-row region placement, or a partitioned relational cluster with synchronous replicas | Reading a message's whole history must be one local read, and the submit acknowledgement must wait on a committed replicated write; interleaving gives the first and synchronous replication gives the second without an application-level transaction | ADR-01 |
| Delivery-state projection: status by message id and by tenant client reference | Bigtable, keyed by message id with a secondary row keyed by tenant and client reference | Google Cloud managed | A wide-column store or a document store with a secondary index, rebuilt from the ledger either way | The projection is disposable and read-heavy at a rate the ledger should not serve; a key-value shape with predictable latency suits a fold result that is rebuilt rather than migrated | ADR-02 |
| Receipt and inbound streams, and the class lanes between submit and attempt | Pub/Sub topics: one per traffic class, one for receipts, one for inbound | Google Cloud managed | A managed Kafka or a durable log with per-partition ordering | The platform needs durability and independent scaling far more than it needs ordering, because the fold is order-independent by construction; separate topics give the class isolation that the shedding order depends on | ADR-08 |
| Per-route pacing against a contracted carrier window | Cloud Tasks, one queue per route with its dispatch rate set from the route table, beneath Memorystore leases | Google Cloud managed | A token-bucket service in front of every attempt, or per-worker budgets with periodic reconciliation | A managed queue with a configurable dispatch rate is a second, independent ceiling that survives a lease-store incident, which turns a potential contract breach into a throughput dip | ADR-07 |
| Route leases and route-quality counters | Memorystore for Redis, one instance per region, atomic block check-out by script | Google Cloud managed | A self-hosted in-memory store per region, or a consensus store if exactness were required | Single-key atomicity on one shard is exactly the guarantee a lease block needs and nothing more; the store is explicitly not authoritative, so its loss costs throughput rather than correctness | ADR-07 |
| Stateless submit, status, attempt execution and ingest workers | Cloud Run services, scaled independently per concern | Google Cloud managed | Containers on a managed Kubernetes cluster, which is what the session plane uses anyway | Four workloads with unrelated load curves need four scaling decisions, and none of them holds state; a request-scaled runtime makes the dormant-tenant cost floor near zero as the cost requirement demands | ADR-03 |
| Long-lived carrier protocol sessions with sequence windows | GKE StatefulSet with stable identities, one pod per bind, drained individually | Google Cloud managed Kubernetes | Dedicated virtual machines per carrier, or sessions co-located in the attempt workers | A bind is an addressable, individually recoverable thing with in-flight state; it needs stable identity and controlled termination, which is precisely what a request-scaled runtime cannot offer | ADR-11 |
| Carrier-approved static egress addresses | Cloud NAT with reserved addresses per region, one range per region allowlisted by each carrier | Google Cloud managed | A NAT appliance or per-carrier VPN and interconnect where a carrier requires private connectivity | Carriers allowlist source addresses by correspondence on their own timetable, so the platform needs a small, stable, reserved set per region rather than ephemeral egress | ADR-11 |
| Message bodies and media, with per-tenant keys and store-no-body mode | Cloud Storage with CMEK per tenant, lifecycle rules per retention setting | Google Cloud managed | An object store with customer-managed encryption and object lifecycle policies — the shape is the same anywhere | Content has a shorter and separately configurable life than the record of it, which requires a store that can be purged independently of the ledger | ADR-14 |
| Carrier credential custody, per-route scoping, tenant-managed keys | Secret Manager for route credentials with Cloud KMS for key material, reached only through a custody service | Google Cloud managed | A secrets manager behind a hardware security module where a tenant requires key custody in hardware | Attempt workers must hold no long-lived carrier material, so the exchange of workload identity for a short-lived single-route token needs a brokered boundary rather than mounted secrets | ADR-11 |
| Consent history, sender-identity registry, route table and jurisdictional rules | Cloud Spanner, append-only consent history and versioned configuration tables, cached in Memorystore with a staleness ceiling | Google Cloud managed | A relational cluster with synchronous replicas and an in-memory cache in front | Consent is read on every submit and must be strongly consistent; configuration is read a billion times and written rarely, so it belongs next to the reader with an explicit invalidation path | ADR-13 |
| Route quality aggregation and evidence analytics | Dataflow streaming jobs into BigQuery, feeding the Memorystore quality store | Google Cloud managed | A stream processor into a columnar warehouse — any equivalent pair | Delivery rate, receipt latency and unknown share are windowed aggregates over the receipt stream, and the same warehouse answers the regulator and the margin question | ADR-16 |
| Audit log retained independently of tenant settings | Cloud Logging sink into a Cloud Storage bucket with Bucket Lock, plus BigQuery for query | Google Cloud managed | An append-only object store with a retention lock and a columnar query layer | A seven-year audit obligation needs write-once retention the platform's own operators cannot shorten, which is a storage-level guarantee rather than an application one | ADR-14 |
| Edge: TLS, tenant credential check, WAF and anycast entry | Global external Application Load Balancer with Cloud Armor | Google Cloud managed | Any anycast load balancer with a managed WAF in front of the submit tier | Submit is a global API with a 99.99% target and hostile inbound endpoints beside it; anycast entry plus managed WAF keeps that off the application's critical path | ADR-03 |
| Build, provenance and deployment of services; separate path for rules and routes | Cloud Build with Artifact Registry and Binary Authorization; Terraform for infrastructure; a reviewed data-publication path for the route table and rule set | Google Cloud managed plus open-source Terraform | Any CI system with image signing and admission control, and any reviewed configuration pipeline | A jurisdictional rule change must take effect without a deployment, which means configuration needs its own reviewed path with the same scrutiny as code rather than a shortcut around the gates | ADR-13 |
| Hostile-receipt testing before staging | A carrier simulator suite as a blocking build gate: late, duplicated, out-of-order, silent and unknown-reference receipts | This design | Contract tests against provider sandboxes, which do not reproduce the pathologies that matter | Production cannot be asked to produce a four-hour-late duplicate receipt on demand, and those are precisely the inputs the fold and the quarantine exist for | ADR-02 |
A submitted message and a carrier call are both "the message" in ordinary speech. Which of them is the thing the platform durably owns, and what happens to the other one when it fails?
Twenty-five thousand receipts a second arrive late, duplicated and out of order. Does the platform keep a status column and update it, or compute status from the facts?
Should the submit call choose a route and confirm the carrier will take the message before acknowledging, so the tenant learns about problems immediately?
A message was accepted by a carrier, no receipt ever arrived, and the deadline has passed. What does the platform tell the tenant?
An attempt times out with no response and the carrier offers no idempotency key. The message may have been delivered, dropped, or never received. Does the platform try again?
A receipt arrives carrying a provider reference the platform has no record of. Is it noise to drop, or data to keep?
A carrier's window of 300 messages per second is contractual, and breaching it suspends a sender identity. How is that enforced across a fleet that scales from two workers to forty?
A tenant submits four million marketing messages while another tenant sends one login code. How does the platform decide what waits?
Is the route chosen when the message is accepted, or when the attempt actually runs?
How much of a channel's strangeness should the platform absorb — template approval, rich cards, media rules, conversation windows — and how much should the API expose?
SMPP binds are long-lived TCP sessions with sequence windows and allowlisted source addresses. Do they live inside the attempt workers, or in a tier of their own?
Somebody texts STOP to a short code four tenants share. Whose job is it to make the messages stop?
Sender-ID registration requirements, quiet hours, template pre-approval and frequency caps differ across 190 jurisdictions and change without notice. Is that code or data?
Message content, the record of what was done with it, and the proof of permission all have retention requirements. Do they share a policy?
A message with a fallback chain may be attempted on two channels at two prices, and a long body is several segments. What is the unit of charge?
A carrier's contract promises a delivery rate. Should the platform route on that promise, or on what it can measure?
Twelve terms used precisely throughout the package, several of which mean something narrower here than in ordinary usage.
| Package | What it is | What it does here | Considered instead |
|---|---|---|---|
| Accepted | The state of a message whose submission has been validated, screened and durably appended to the ledger, and acknowledged to the caller. | The platform's promise of ownership, and the only thing the submit path ever promises. | Often read by tenants as "sent", which it deliberately is not — hence the separate vocabulary in the API. |
| Attempt | One carrier call for one message, with its own route, segment breakdown, provider reference, outcome and price. | The unit of retry, of routing, of rating and of the fold that produces delivery state. | Sometimes called a delivery or a send, both of which imply an outcome the attempt does not carry. |
| Receipt | An asynchronous statement from a carrier about an attempt, arriving seconds to hours later, possibly duplicated, out of order, or for an unknown reference. | Evidence. It is appended and retained for thirteen months, longer than the body it describes. | A delivery report or DLR in carrier protocols, which overstates how definitive it is. |
| Unknown | The terminal state of a message whose time-to-live elapsed with no conclusive receipt. | A first-class answer in the API, the SLOs and the route-quality signal — never collapsed into delivered or failed. | Usually reported elsewhere as delivered, which is the lie this platform is designed not to tell. |
| Ambiguous submit | An attempt that timed out with no response on a route offering no idempotency key, so neither delivery nor non-delivery can be inferred. | A recorded fact resolved by per-route policy, with the policy applied written onto the message. | Commonly treated as a plain timeout and retried, which is how duplicate login codes are made. |
| Route | A tuple of channel, destination jurisdiction, provider and sender-identity type, carrying price per segment, a contracted throughput window, supported features, observed quality and health. | The unit of pacing, failover, pricing, policy and commercial relationship. | Loosely called a carrier or a connection, which conflates the provider with the specific lane bought from them. |
| Lease | A block of permits checked out from a route's contracted throughput window and spent locally without further coordination. | How a contractual window is held across an elastic fleet with no coordination hop per message. | A rate limit, which understates the penalty: breaching a window suspends a sender identity. |
| Sender identity | The thing a recipient sees as the origin: a long number, a short code, an alphanumeric sender ID, a toll-free number or a channel business identity, with the jurisdictions it is registered in. | The asset a throughput breach or a compliance failure actually damages, and the key by which inbound traffic finds its tenant. | Often called the "from" number, which hides that it is a registered, suspendable entity rather than a field. |
| Traffic class | One of interactive, conversational or bulk, declared at submission. | Decides the lane, the route preference, the SLO and the position in the shedding order. | A priority, which implies a single ordering rather than genuinely different products. |
| Client reference | A tenant-supplied identifier, unique per tenant, carried on the submission. | The idempotency key that makes a resend return the original message, and the only identifier a tenant's support desk has. | An external id or correlation id; here it has enforced uniqueness and a dedup window, which those usually do not. |
| Regulated keyword | An inbound message such as STOP, UNSUBSCRIBE or HELP, or a locale equivalent, whose handling is mandated rather than optional. | Intercepted by the platform before the tenant is told, with the consent change written first and a mandated reply sent. | Treated elsewhere as tenant application logic, which is how a STOP on a shared number ends up unenforced. |
| Projection | A rebuildable read model derived by folding a message's attempts and receipts, served for status queries. | The thing tenants read. It may lag, must be rebuildable in hours, and is never the authority. | A status table, which looks the same and is authoritative, which is the difference this package is about. |
svg/<view>.svg or drawio/<view>.drawio in draw.io Desktop or at app.diagrams.net to edit. The SVG carries the diagram inside it, so it is both the picture and the source. This folder is self-contained — copy it whole and every link still resolves.