Outpost logoOutpost Docs

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:

  1. Schema check at the API edge - is it even shaped like E.164?
  2. Static parse - is it a dialable number for its country?
  3. 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.

ChannelOpt outOpt back in
Two-way SMS keywordSTOP, UNSUBSCRIBE, CANCEL, END, QUIT, STOPALLSTART, UNSTOP, YES
API (admin)POST /phones/:phone/opt-outDELETE /phones/:phone/opt-out
Public messaging status pageform on /o/:tokensame 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

Calling from TypeScript? See Generating a Typed Client.

On this page