Troubleshooting: channel and conversation pre-send refusals
A send on any messaging channel runs a short set of deterministic checks before it is ever handed to a provider. When one of those checks refuses the request, the structured 404/422 envelope says exactly why — and the codes that fire carry either achannel value or a conversation_id back, so you can
tell which field to fix. This page is the routing card for the five hardest
channel- and conversation-naming refusals a POST /api/v1/messages (and the
POST /api/v1/notify multi-channel fan-out, which dispatches through the
same send service) can throw. Definitions live in the
Error Code Reference; this page only owns the triage
path.
All five refusals are deterministic — the same body fails the same way
until you fix the named field. Never loop the retry.
Section routing — which refusal fired
CHANNEL_REQUIRED — the send did not name its channel
Cause. The request body had no channel, an empty string, or the literal
"auto". After 2026-09-01 every send must name its channel explicitly — the
unified send path used to guess one on null/"auto", and measured behaviour
was that it picked VOICE for a text message on most input combinations
(paying PSTN rates and ringing the recipient’s phone on an SMS send). Rather
than guess wrong again, the gate refuses the request outright.
Fix. Pass a valid channel name in the body — one of the enums on
channels, e.g. sms, whatsapp, rcs, email, voice. If you
genuinely want the platform to pick a channel for you, do not send
channel: "auto" here — call
POST /messages/smart-send
instead, which returns the chosen channel and its reasoning.
Envelope sample.
CHANNEL_COMING_SOON — a gated channel on a tenant that has not been enabled
Cause. The channel enum was valid, but it points at a regional or beta
channel that has not been enabled for your organization (e.g. an APAC
preview surface, or a channel your plan does not yet cover). The check is
tenant-scoped — the same channel may send fine for another tenant.
Fix. This is a tenant-side wait-for-enablement gate. Open a support ticket
asking for the channel to be gated in, listing (1) the exact channel name, (2)
your organization id (Settings → Organization, or GET /api/v1/me), and (3)
the intended destination region. Do not fall back to a sibling channel
unless the recipient can be reached there too — the refusal exists because a
beta channel’s deliverability is not endorsed for general tenant traffic.
Envelope sample.
CHANNEL_NOT_CONFIGURED (the channel is enabled, but
the provider credentials/route for it were never completed under
Settings → Channels) or CHANNEL_UNAVAILABLE (enabled, but the fallback
chain was exhausted — see
channel unavailable and fallback exhausted).
CHANNEL_NOT_REPLYABLE — reply via an inbox-only label
Cause. In the Inbox / conversation-reply flow (and the
continue-on-channel surface on POST /conversations/:id/resume-channel),
the chosen channel string was one of the inbox-only triage labels — agent
(assign to an agent) or video (video call) — which are filters over the
conversation list, not outbound providers.
Fix. Pick a real outbound channel — sms, whatsapp, email, voice,
or another reachable one — from the reply composer. An agent or video
label exists only to route the thread; it never places an outbound provider
dispatch.
Envelope sample.
MISSING_IDENTITY — the label hits
CHANNEL_NOT_REPLYABLE only when the channel is an inbox view; a real channel
missing the recipient’s identity on the conversation row throws the 400
MISSING_IDENTITY instead. Both are deterministic — fix the named field.
CONVERSATION_NOT_FOUND — conversation_id resolved to nothing
Cause. POST /messages (and the POST /notify cascade, which re-uses
the same send service) was asked to thread the send into an
existing conversation via an optional conversation_id, but the lookup found
no such conversation on the caller’s tenant — either because the id is
well-formed but unknown, or because it belongs to a different tenant. This is
an idempotency scope guard: a reply can never be threaded into a foreign
tenant’s conversation by accident.
Fix. Drop the field entirely to let the send auto-resolve the thread (the
recipient + channel will open a new conversation or reuse the latest open
one), or re-query to get the right conversation id via
GET /conversations?contact=… before threading.
Envelope sample.
CONVERSATION_TERMINAL — a conversation that exists but is closed,
archived, or snoozed, and so cannot accept replies on any channel.
CONVERSATION_MISMATCH — conversation_id belongs to a different recipient/channel
Cause. The conversation_id did resolve — but either the conversation’s
contact_phone/contact_email does not equal the send’s to recipient, or
the conversation’s primary channel (and its cross-channel channels[]
union) does not include the send channel. This is the anti-misroute guard:
it stops a reply being threaded into another contact’s open thread (or an SMS
reply stapled onto a WhatsApp thread).
Fix. Give the correct conversation_id — the one whose recipient and
channel actually address this send — or omit the field so the send
auto-resolves the thread by recipient. Query with
GET /conversations?contact=<to> to pick the right row.
Envelope sample.
Where each refusal routes
CHANNEL_REQUIRED—POST /api/v1/messages(its explicit-channel guard runs first). CallPOST /messages/smart-sendto have the platform choose a channel and report it back.CHANNEL_COMING_SOON— the custom-channel gate inside the message-send bootstrap; enable the tenant over support before the channel will route.CHANNEL_NOT_REPLYABLE— the/conversations/:id/replyflow and thecontinue-on-channelsurface; pick a real outbound channel.CONVERSATION_NOT_FOUND/CONVERSATION_MISMATCH— the anti-misroute thread guard onPOST /api/v1/messages(and thePOST /notifycascade that re-uses the send service). Field name isconversation_id.
When to escalate
ACHANNEL_COMING_SOON on a channel you believe is released to your
plan is abnormal; so is CONVERSATION_MISMATCH on a thread id that you can
see in the Inbox itself. Escalate to support with:
- Your organization ID — Settings → Organization, or
GET /api/v1/me. - The exact code and
meta.request_idfrom the rejected envelope. - For
CHANNEL_COMING_SOON: the channel name and the destination region. - For
CONVERSATION_NOT_FOUND/CONVERSATION_MISMATCH: theconversation_idyou passed, the conversation id you see in the Inbox for that thread, and the recipientto+channelof the send.
See also
- Error Code Reference — definitions for every code routed here.
- Messaging queues and receipts —
the non-error parking states (
queued,scheduled,pending) that hold a send before any channel gate runs. - Send-price and country gates — the price-ceiling and country-allowlist gates that fire beside these.
- Pricing-gate errors — the billing-family
PRICING_*codes (rate card, FX source, override writes) on the pricing side of the send path. - Template variant missing —
the pre-send variant-resolution refusal
NO_VARIANT_FOR_CHANNEL. - Pre-send gates on messaging — the blocked-contact, quiet-hours, and session-window gates that share the same pre-send chain.
- Smart send — the
POST /messages/smart-sendpath to have Orbit choose a channel.