Messaging Overview
Messaging runs as its own service at messaging.sip.io, a pure HTTP + data plane (no SIP/media), separate from the voice edge but sharing the same account, numbers, and data model. It does two things: receive inbound messages from your providers and send outbound ones.
Channels
Section titled “Channels”| Channel | Status | Notes |
|---|---|---|
| SMS | Live | Carrier-neutral (BYOC) via a config-driven HTTP adapter. |
| MMS | Live | Media is carried on an SMS/WhatsApp message (the media array), not a separate channel. |
| Live | Cloud API; templates + 24-hour session window. | |
| RCS | Roadmap | Modeled (rcs channel, RBM agent address), but no provider adapter yet. |
The data model
Section titled “The data model”Five tables, all account-scoped:
| Table | What it holds |
|---|---|
messaging_channel | A sender: channel (sms/whatsapp/rcs), provider (adapter key), address, provider refs, and an optional default_queue_id. UNIQUE(channel, address). |
conversation | One thread per (channel_id, contact_address): status (open/assigned/closed/snoozed), assignment, unread, and the WhatsApp window_expires_at. |
message | direction (inbound/outbound), body, media, status, provider_message_id, and the template_id used. |
message_template | WhatsApp/RCS templates: name, language, category, body with {{1}} placeholders, approval status. |
message_opt_out | The per-account STOP list: (channel, contact_address). |
A contact’s whole history lives in one conversation; messages link to it by conversation_id.
Sending
Section titled “Sending”curl -X POST https://messaging.sip.io/send \ -H 'x-api-key: sk_…' -H 'content-type: application/json' \ -d '{ "channelId": "ch_…", "to": "+14155550100", "body": "Hi from SIP.IO" }'POST /send requires the messaging scope. Body: { channelId, to, body | templateName | mediaUrls, templateLang, templateVars }. It:
- blocks opted-out recipients (
403), - enforces the WhatsApp 24h window: outside it, a
templateNameis required, else422, - dispatches through the channel’s adapter and stores an outbound
message.
Returns { ok, messageId, conversationId, providerMessageId, status }.
Receiving
Section titled “Receiving”Each channel has a public inbound URL, POST /inbound/{channelId}, that you register with your provider. On an inbound message the service:
- normalizes the provider payload through the channel’s adapter,
- handles STOP/START opt-out keywords,
- upserts the conversation (reopening a closed one, resetting the WhatsApp window),
- stores the inbound
message.
Provider status callbacks (delivered/read/failed) arrive on the same URL and update the message by provider_message_id.
Message status is one of queued, sent, delivered, read, failed, received.
Carrier-neutral adapters
Section titled “Carrier-neutral adapters”Providers are pluggable behind a small adapter interface, selected by the channel’s provider field:
generic_http: a regional SMS carrier or aggregator defined entirely in the channel’smetadata(the request URL, auth, and field mapping), so no code is needed to add a carrier.- WhatsApp Cloud API, for the
whatsappchannel. - SMPP and RCS/RBM adapters are planned.
Roadmap
Section titled “Roadmap”A /v1/messages REST surface on the public API, the omnichannel inbox + routing of inbound conversations into the contact center (the model is in place: default_queue_id, assignment), a message.received webhook, WhatsApp template management, inbound signature verification, and the RCS and SMPP adapters.