Communications & Campaigns
Optional grouping layers with deduplication and stats rollups.
Communications and campaigns are optional organizational layers above individual messages. You never need them to send, but they provide grouping, deduplication, and aggregated stats.
In the dashboard: Sending & Tracking Messages shows how to browse campaigns and drill into a communication's recipients.
How they relate
- A communication is a logical batch treated as one unit - a single broadcast to a list of recipients. It is the deduplication key: the same phone can never receive two messages with the same
communicationId. Duplicates are rejected at the API before anything is queued. - A campaign groups one or more communications over time - a product launch running several sends over weeks. It has no deduplication semantics of its own; it exists purely for aggregation and analytics.
Stats (message count, cost, delivery counts) roll up from messages to communications to campaigns - see Stats.
Auto-materialization
Downstream services already have their own campaign and communication concepts with their own IDs. Outpost doesn't make them pre-register anything: callers pass communicationId and campaignId directly on the send request, and Outpost creates the records automatically on first use. This is called auto-materialization.
POST /v1/messages/sms
{
"items": [{ "to": "+15551234567", "body": "Hello" }],
"communicationId": "broadcast-2026-q1",
"campaignId": "q1-promo"
}The first send referencing these IDs creates the records; later sends load them. Creation is race-safe - concurrent first sends resolve to exactly one record.
Tenant locking
An auto-materialized communication or campaign is stamped with the sending tenant's ID. From then on, no other tenant can use that ID - a send from a different tenant referencing it is rejected with a 409 conflict. This prevents one tenant's grouping (and its stats) from leaking into another's. Admin keys, which act across tenants, are exempt.
Browsing and stats
Both entities are readable via the API and dashboard:
GET /v1/communications/GET /v1/campaigns- list with search and filters, optionally with stats (withStats=true)GET /v1/communications/:id/GET /v1/campaigns/:id- details with aggregate stats (delivery rate, cost, counts)
Design notes
Auto-materialization is a pragmatic bridge for existing integrations. The long-term direction is for communications and campaigns to become first-class citizens - created explicitly with richer configuration - without breaking implicit creation for current callers.
API reference
GET /v1/communications- list communicationsGET /v1/communications/:communicationId- details with aggregate statsGET /v1/communications/:communicationId/messages- a communication's messagesGET /v1/campaigns- list campaignsGET /v1/campaigns/:campaignId- details with aggregate stats
Calling from TypeScript? See Generating a Typed Client.