Skip to main content

Extension SMS

Extension SMS is two-way texting from a user’s voice number. Every SMS where an extension’s DID is sender or recipient is rolled up into an inbox-style conversation list, threaded by the other party — the softphone-texting surface desk-phone platforms ship as table stakes. It is deliberately separate from the bulk Messages API: POST /api/v1/messages is a fan-out send surface with no per-extension inbox, while Extension SMS is the same-person conversation view an operator lives in. Typical use: a support agent whose softphone rings on +13035550142 also answers customer texts from that same number, without switching tools. Four endpoints back the surface, under /api/v1/voice/extension-sms:
  • GET /threads — the inbox: per-peer conversation summaries with unread counts, newest first.
  • GET /thread — one conversation: every SMS exchanged with a single peer, newest first.
  • POST /send — send an SMS from the extension’s DID.
  • POST /read — mark a peer’s inbound messages read.

Access and roles

The dashboard page at Voice → Extension SMS is visible to owner, admin, and developer roles; other roles see an access-restricted notice. The API enforces two layers independent of the page:
  • Reading (GET /threads, GET /thread) requires the voice:read or voice:write scope.
  • Writing (POST /send, POST /read) requires an owner, admin, developer, or agent role and the voice:write scope, server-side. The dashboard’s role gate is a UI affordance — an API key with only voice:read gets a 403 on /send even though reads succeed.
Treat the thread list and message bodies as conversation PII, and scope API keys accordingly.

Read the thread list

Open Voice → Extension SMS. The number picker at the top lists the organization’s active phone numbers (SMS-capable numbers sort first) — pick the DID whose inbox you want. The left column is the thread list. Each row is one contact: the peer number, a preview of the most recent message, its relative timestamp, and an unread badge when unread inbound SMS exist for that peer. Rows with newest activity float to the top; the list refreshes roughly every 30 seconds while the page is open. A thread’s unread count covers inbound messages only — your own outbound texts never badge you. Opening a conversation marks it read automatically (see below), and the badge clears on the next refresh. The same roll-up is available over the API:
Page with limit (1–100, default 50) and offset. GET /thread then loads one conversation, newest first, up to 200 messages per page. Both endpoints reject a from number the organization does not own as an active DID with a 403 — see Operating limits.

Send an SMS

In the dashboard, open a conversation — or start a new one with New message — and type into the compose box. The composer enforces the API’s 1,600-character ceiling and rejects a blank body or an invalid recipient format. Delivery status appears on the message, and a failed outbound carries a human-readable failure reason (carrier rejection, invalid destination, sender restriction) rather than a bare failed badge. Over the API:
The endpoint returns 202 — the message is accepted for delivery, not yet delivered. Poll the thread view (or a delivery webhook) for the final status. An optional media_url attaches one MMS asset (HTTPS URL); blank values are ignored, so serializing an empty field on a plain-text send is safe.

Common send rejections

  • 403 FORBIDDEN — the from number is not an active DID the organization owns. Check the number picker; this check runs on all four endpoints.
  • 422 RECIPIENT_OPTED_OUT — the recipient opted out of SMS; no further sends to them succeed until they opt back in per your organization’s opt-out policy.
  • skipped status with a reason — a tenant-owned suppression policy accepted but held the send (for example, duplicate_content when the exact same body already reached that recipient inside your duplicate-suppression window, or opted_out). A skipped result releases any consumed monthly quota slot, matching the opt-out path.
The from number is written on every send and the action lands in the audit log, so outbound texting is attributable to the user who sent.

Mark a thread read

Unread counts follow the inbound messages that still have no read marker on them. When you open a conversation in the dashboard, its inbound leg is marked read automatically; the call is idempotent and records which user cleared it, so a re-mark never overwrites the original reader or timestamp. Over the API:
markedRead is the count of inbound messages newly marked — 0 if the thread was already read. Like the threads roll-up, only the inbound direction counts; outbound messages are never part of an unread badge.

Operating limits

  • Owned, active numbers only. Every endpoint validates from against your organization’s active phone numbers before reading or sending. Anything else is refused (403), and text-spoofing a released or foreign number is not possible.
  • Body size. 1–1,600 characters per SMS; longer texts must be split client-side or sent as an MMS attachment via media_url.
  • Rate limits. Reads and writes use the standard authenticated read/write rate limits, same as the rest of the voice surface.
  • Delivery attribution. Every send writes an audit-log entry with the from number, destination, and acting user.

Compliance and quiet hours

Extension SMS sends go through the same delivery pipeline as every other SMS in your organization, so the tenant-owned gates you have configured apply here exactly as they do for campaign or API sends:
  • Opt-out lists — a recipient who opted out gets a 422 (RECIPIENT_OPTED_OUT) instead of a delivery.
  • Quiet hours — if your organization has opted into per-channel quiet hours (organization settings), an Extension SMS send is evaluated against them like any other send; transactional traffic resolves as transactional and flows 24/7, while sends tagged as marketing are blocked during the restricted window. If you have not enabled quiet hours, sends are unrestricted. Schedule-sensitive texting (for example, appointment reminders an agent sends at night) should be understood against your organization’s quiet-hours settings, not assumed outside them.
  • Duplicate-content suppression — if your organization enables the duplicate-message window, a repeated identical body returns skipped with reason: "duplicate_content".
Inbound-only reading (GET /threads, GET /thread) is unaffected by these gates.

Extension SMS vs the Messages API

Use the Messaging endpoints for broadcast, campaign, and programmatic fan-out sends. Use Extension SMS when the workflow is one operator conversing with one contact from their own number — the inbox metaphor, not the blast metaphor. Both surfaces read and write the same underlying message records, so a conversation started in the dashboard is fully visible to API consumers and vice versa.