Phones & Opt-Out
The global phonebook, validation, and regulatory opt-out management.
Outpost maintains a global phonebook: every phone number the system has ever seen is stored as a phone record, keyed by its E.164 form (+15551234567). The phone is the primary "identity actor" - messages, events, and several stats dimensions hang off it.
In the dashboard: Phones & the Number Pool shows how to inspect any phone's validation, opt-out state, and history.
How it relates
Phones are global, not tenant-scoped. The same number is a single record shared by every tenant that messages it. This is deliberate: opt-out is a regulatory commitment to the phone's owner, not to a particular tenant. If a person texts STOP, no tenant - current or future - may message them again. Outpost tracks which tenants and projects have messaged each phone, but no tenant owns the record.
Validation
Numbers are validated in three layers, cheapest first:
- Schema check at the API edge - is it even shaped like E.164?
- Static parse - is it a dialable number for its country?
- AWS validation - a call to AWS End User Messaging determines carrier, type (mobile / landline / VOIP), and SMS capability. Results are cached on the record, so a valid phone never costs a second validation call.
Landlines and unsupported types are marked isValid: false and will never be sent to - but the record is kept, so rejections are auditable and repeat sends don't re-pay for validation. Phones are never deleted.
Opt-out and opt-in
Phones are opted in by default. Opting out blocks all messages to that number regardless of tenant, project, or communication.
| Channel | Opt out | Opt back in |
|---|---|---|
| Two-way SMS keyword | STOP, UNSUBSCRIBE, CANCEL, END, QUIT, STOPALL | START, UNSTOP, YES |
| API (admin) | POST /phones/:phone/opt-out | DELETE /phones/:phone/opt-out |
| Public messaging status page | form on /o/:token | same page |
SMS keyword opt-in/opt-out only works for recipients in countries covered by the phone pool's dedicated numbers. For everyone else (shared routes), replies are silently lost - which is why Outpost appends an opt-out link footer to those messages. See Messages - compliance footers.
Enforcement is double-checked
Opt-out is enforced twice: at the API (the item is rejected before a message record is created) and again in the send worker just before the AWS call. The second check catches the race where a phone opts out after the API accepted a message but before it was sent.
The public messaging status page
Every phone record carries an unguessable random token. GET /o/:token renders a minimal page showing the phone's current status with a button to change it; the state change only happens on POST, so link-prefetching bots can't opt anyone out. These are the only unauthenticated routes in the API, and they are rate-limited.
Phone-level stats
Each phone record accumulates its own counters - total messages, delivered/failed/blocked counts, and total cost - alongside the system-wide stats dimensions (phone counts and opt-in/opt-out counts by country, tenant, and project).
API reference
GET /v1/phones- list and search phone recordsGET /v1/phones/:phone- one phone's record and countersPOST /v1/phones/:phone/opt-out- opt a phone outDELETE /v1/phones/:phone/opt-out- opt a phone back inGET /v1/phones/:phone/events- a phone's event history
Calling from TypeScript? See Generating a Typed Client.