Group messaging model and routing claim election
Four primitives share the messaging surface and get confused with each other more than any other pair in the platform:- a group flow sends one payload to many recipients in a single outbound operation,
- a thread is a shared conversation object on a rich channel,
- a routing claim elects the one owner of a conversation, and
- a label sticks static annotation on already-assigned work.
POST /messages/group), and gives the decision matrix between the group flow, the batch fan-out, and the campaigns module. Readers reaching for “label” colloquially to mean routing — or treating a group send as a thread — are the origin of most workflow design errors here, because carrier reality forbids the conflation: SMS and MMS have no shared conversation object (group is fan-out), and voice rings and inboxes hold one claim at a time (routing is election, not tagging).
Group flow: fan-out, not a thread
“Group Messaging” on this platform means the capability fixed in the Group Messaging glossary entry: a text conversation logically shared among three or more recipients, where every member receives each message. Carrier reality, however, is that SMS and MMS cannot hold a joint thread. Group SMS is delivered as fan-out — one copy per recipient — and MMS group mode likewise fans one payload out to up to twenty recipients in a single call.POST /messages/group on the MMS channel is the platform’s group-MMS surface: every recipient is pre-inserted as a pending row, each per-recipient send carries its own idempotency key within the group, and per-recipient status returns in the same response.
Why the rich-channel inbox is the thread: the unified inbox maintains one conversation object across participants on channels that can carry shared history. That object — not any outbound group send — is the only living thread the platform has. If you need per-recipient receipts on SMS or MMS, use the group flow; if you need a true shared conversation, use the inbox thread. Outbound still exits over the Devotel wholesale softswitch either way — the group choice never changes where traffic terminates.
The envelope, step by step
A group send runs three phases, and the response tells you outcome per recipient rather than a single aggregate verdict:- Envelope validation.
toaccepts 1–20 recipients in the same call with one shared payload —body,media_urls, or both (at least one is required; an MMS with neither is a billing-only event). Duplicate phone numbers in one request and an envelope exceeding the 20-recipient cap are rejected with 422 before any row is created. - Pre-flight balance gate. The route checks the wallet covers the recipient count at a conservative floor per recipient before it persists anything, returning 402
INSUFFICIENT_BALANCEon the obvious shortfall; the per-recipient balance check inside the per-message send pipeline remains the authoritative gate. - Persist, then attempt. Every recipient is pre-inserted as a
pendingrow in one transaction, then the per-message send pipeline runs for each recipient in turn. The row exists in your messages table regardless of outcome — a recipient you never see in the listing is impossible.
group-mms:${group_id}:${to} — one stable key per recipient, all under one group_id. A retried request re-keys on the persisted row rather than double-sending, and the key travels with the row through the audit trail. The response’s messages array echoes message_id, to, and final status per recipient in request order, with a summary roll-up of total, succeeded, failed, and scheduled.
The NANP gate and MMS_NANP_ONLY
MMS on this platform terminates to NANP destinations only. A recipient outside the North American Numbering Plan is rejected during the per-recipient send pipeline withMMS_NANP_ONLY, and that recipient’s pre-inserted row flips to failed with the rejection code. The failure is contained: the rest of the group still dispatches, and the response reports the failed recipient alongside the succeeded ones.
Because the gate evaluates per recipient, a mixed group — some NANP, some not — returns an explicit partial verdict rather than a silent whole-group failure. Keep NANP-only distribution as a contract of the channel, not a mistaken assumption of your caller.
Fail-safe semantics: no orphan recipients
Per-recipient failures never vanish a recipient. A throw from the send pipeline, a schema-level deliverability rejection, or a carrier-side terminal status all flip that recipient’s pre-inserted row tofailed and record the error code on the row — the recipient still appears in your messages listing with a meaningful status and the response carries error_code + error_message for that recipient.
The response code mirrors the mixed outcome: 207 Multi-Status when any recipient failed (even if all failed), 200 when every recipient succeeded. Client code should branch on the body, not the HTTP status alone, but the status does signal “look at the body” when it is 207.
Provider boundary
The route documents MMS termination over the platform’s named carrier exception for MMS (the Devotel softswitch does not yet carry MT MMS, so outbound MMS exits over the named exception provider through the standard router — no provider bypass). What the page asserts is exactly that boundary and nothing more; routing claims beyond it belong in the channel page, not here. Outbound voice and SMS continue to exit only via the Devotel wholesale softswitch — the group envelope never changes where a non-MMS payload terminates.Choosing the right fan-out surface
The group flow is one of three one-to-many surfaces. Pick the one whose cap and semantics match your intent:
Per-recipient deltas (different body or media per recipient) belong in the batch surface, not the group envelope — the group flow is by design one payload to many recipients. The campaigns module owns the marketing workflow; the group and batch surfaces own the transaction-shaped fan-out. The group flow’s
scheduled_at field schedules the whole group under one envelope, while batch and campaigns own their own scheduling models.
Sender election: how a shared thread is answered
A group flow answers “which recipients see this,” not “who will reply.” A returning message — or an inbound voice call — has to go to exactly one owner. The platform resolves that ownership with routing claim election: the claim module never runs two competing claims on one conversation at the same time, so one claim per conversation is the invariant everything else builds on. For the inbox the claim is a conversation-level assignment through the digital queues: dispatch checks agent presence, reads the capacity ledger, and claims the item on the winning agent’s connection. For voice rings the claim elects among ring-group members by strategy — first-available, round-robin, or fewest-calls. Either way, election ends in one durable assignment row that every read surface agrees on. A label applied anywhere in that process is annotation on the already-assigned work, never a second claim.Labels versus routing claims
A label is a note, not a vote. On the work-item lifecycle, a label marks static metadata on a conversation — a tag that routing, reporting, or QA can read — whereas a routing claim holds the conversation’s ownership commitment. You pick a label-style response when the response must not change who owns the item; you pick a claim when it must exclude every other claim. The disposition lifecycle under the Inbox API reference governs the claim states — open, pending, resolved — and a label applied to a sealed item mutates the annotation, never the lifecycle. Refer to that lifecycle before treating any label as routing truth.The four primitives side by side
Rules that prevent conflation
- A group flow never carries thread semantics back. Each recipient’s copy is independent; replies elect claims on one-to-one threads.
- A label never moves ownership. Dispatch never reads the label store; only an elected claim changes who owns an item.
- One conversation holds at most one claim at any moment. Two competing claims on the same item is the forbidden state.
- A thread exists only where the channel can share it. On SMS and MMS every “group” is fan-out; the thread lives on rich channels only.
- Keep the names straight. “Label” means annotation. Ownership moves only through claims — inbox election or a disposition against the lifecycle — never through labels.
Choosing between them
How the four surfaces interact
- Group flow → fan-out. The outbound group operation fans one payload into per-recipient copies; it never creates a thread.
- Routing claim → owner. Each return path elects exactly one owner, discharged through open, pending, resolved.
- Label → annotation. Labels ride on work a claim already owns, for routing filters, QA, and reporting.
- Thread → rich client. The inbox renders the single shared conversation object that SMS and MMS cannot carry.
POST /messages/group carries the envelope contract this page documents; the MMS channel group flow carries carrier-level fan-out receipts; the batch fan-out model contrasts group with per-recipient delta fan-out; the message envelope model declares the per-hop envelope a group send fills; the campaign lifecycle shows the marketing fan-out surface beyond the group’s 20-recipient cap; the inbox setup guide runs claim assignment through digital queues; the routing-claim election lifecycle is the destination-side model every claim defers to; and cascade failover groups bind every fallback hop of a send into one logical group — a group of legs within a single send, not a group of recipients.