Troubleshooting: message stuck in queued
A message that stays atqueued has not been sent to a provider yet — it
is parked before the provider handoff. That owning split is deliberate:
this page owns the queued lane end-to-end — the cause table for the
holds that give that state, the unstick path, and the escalation path. The
sister page message parked before sending
owns the three other pre-send holds — scheduled, pending, and
quiet-hours — and defers a row clearly at queued back here. A row that
already passed the queue — submitted_no_receipt, no delivery receipt —
belongs to the
message sent but no delivery receipt
page.
This page also carries the status decoder — the status ladder and what each
status means for diagnosis — because queued is the centre of the ladder.
How message statuses fit together
Every message moves through a defined set of statuses, and each transition is gated — a message cannot jump fromqueued straight to delivered, and a
terminal status like read or failed cannot move backward. The happy path
for an immediate send is:
scheduled until its send_at
time arrives, then joins the queue:
accepted (the synchronous pre-queue handoff), test_sent
(sandbox mode), and cancelled (a scheduled message you cancelled before it
sent) exist alongside the main path. Status updates are ordered, so each row
can only move forward along an allowed transition — a stale carrier callback
cannot drag a settled message back to queued.
Where to check the status
- Delivery Log: open Messages → Tools → Delivery log and search the
message ID (
msg_…), the provider reference, or the recipient. Filter by the Status filter to isolate stuck rows — for example/messages/delivery-log?status=queued&direction=outboundshows every queued outbound message. See the Delivery Log guide. - Per-channel workspace: SMS, WhatsApp, and Email channels each show outbound history with the same status values.
- API: call
GET /api/v1/messageswith astatus=queuedfilter, or fetch the row by ID. Thestatusfield tells you where the row is; the response also tells you whether the row carries asend_at(scheduled), or a provider reference once it reachessent.
The queued debug ladder
Work this ladder top to bottom when a row sits atqueued longer than you
expect — it ends at either a tenant-owned fix or a support bundle, without
leaving this page:
- Isolate the lane. Filter the Delivery Log to
status=queued&direction=outboundso you are looking at exactly the rows this page owns, not the parked rows the sister page covers. - Confirm over the API.
GET /api/v1/messages?status=queuedreturns the same lane as JSON;GET /api/v1/messages/:idgives the authoritative row —status,send_at, channel, sender pool, and campaign. - Match the row to the cause table. Each field from step 2 maps to a
row in the cause table: a
future
send_atis the scheduled hold, a campaign points at frequency caps and quiet hours, an exhausted pool points at sender capacity. - Fix the cause, then retry once. A retry against an uncorrected hold re-queues the same row — see What not to do.
- Not a hold you own? Escalate with the bundle. If no tenant-owned
cause matches and the lane is stuck past a few minutes, open a ticket
with your tenant ID and one
msg_…ID — see When to escalate for where both live.
Common causes and how to unstick them
Work through these in order — they cover the overwhelming majority of queued-for-longer-than-expected rows.Split by state, one owner per state. Rows parked at
scheduled, pending,
or a quiet-hours hold belong to the sister page
message parked before sending — it owns
those holds as their own states. Rows parked at queued stay here — this
page owns that lane. A row that moved on to submitted_no_receipt left the
queue entirely — see
message sent but no delivery receipt.Diagnosis checklist
- Get the message ID. Open the Delivery Log or your channel workspace
and copy the
msg_…ID from the row. - Read the message detail. Open the full message detail page — it shows the send channel, the sender pool, the scheduled time if one exists, and the campaign it belongs to. Each of those fields maps to a row in the cause table above.
- Check the campaign. If the message came from a campaign, open that campaign’s settings: frequency caps, quiet hours, and sender pool are configured there.
- Check tenant-level compliance settings. Quiet-hours and sender-registration gates are organization-wide. Confirm the hold is intentional before you clear it.
- Query the API if the dashboard shows conflicting data.
GET /api/v1/messages/:idreturns the authoritativestatus,send_at,channel, and any provider reference.
What not to do
Do not spamPOST /api/v1/messages/:id/retry while the cause is unknown. A
retry against a quiet-hours hold, a pending sender approval, or a capped growth
window just moves the same blocked message through the same gate again. Fix the
cause first, then retry.
Also avoid re-sending a fresh copy of the message to an end user while the
original row is still queued — once the hold clears, both copies go out, and
your recipient receives the same content twice.
DLR recovery and webhook DLQ replay do not unstick queued
Two recovery tools sit one stage later on the ladder and are easy to
mis-apply to a stuck queue row:
- DLR recovery (Recover a failed or late
DLR) retraces a delivery receipt
for a row that already reached
sent— the provider accepted the message and the carrier outcome went missing. Aqueuedrow never reached the provider, so there is no receipt to recover. - Webhook DLQ replay (DLQ and recovery) re-delivers events Orbit already emitted after your endpoint exhausted the attempt budget. A row parked before the provider handoff has not emitted its send events yet, so nothing for it sits in the DLQ.
queued, the levers are the ladder and the cause table
above. Reach for DLR recovery only once the row reads sent or
submitted_no_receipt, and for DLQ replay only when your handler missed
events that did fire.
Rate limits and quotas that hold the queue
When sends return 429 while rows sit atqueued, the error code tells you
whether the queue is draining or parked:
The distinction that decides your next move: a per-second throughput 429
means dispatch ran ahead of what one sender may carry per second — the
queued rows under it are draining, not stuck, and a blind retry only
re-joins the same queue. A period quota does not clear on backoff at all:
the row parks until the cap changes or the window resets. Raise the cap or
wait the cycle; if the reset passes and the lane has not moved, that is
your escalation trigger — bundle it with the tenant ID and message ID from
When to escalate. The full 429 taxonomy and the backoff
pattern live in Rate limits and cool-downs.
When to escalate
If you worked through the checklist, the cause is not a hold you control, and messages are still stuck for more than a few minutes, open a support ticket and include both of these so we can trace the row without a back-and-forth:- Your tenant ID (shown in the dashboard under Settings → Organization,
and returned by the
GET /api/v1/meresponse asorganizationId). - The message ID of one stuck row (
msg_…).
currentDay and todayCap values from GET /api/v1/email/warmup-status —
so support can tell a deliberate ramp hold from a real fault on the first
reply.
That pair lets support pull the exact queue entry and the exact send-gate
evaluation without you having to re-describe the symptom.
Status decoder
Every status value the Delivery Log filter and theGET /api/v1/messages
response can return, and what it means for diagnosis:
If a row is sitting on any of the blocked-in-queue statuses (
queued,
scheduled, pending, accepted, sending) for longer than you expect,
the cause table and checklist above will get you to the hold faster than a
blind retry.
See also
- Message parked before sending — the other pre-send holds (
scheduled,pending, quiet-hours) as their own state matrix - Message sent but no delivery receipt — the post-queue stage
- Recover a failed or late DLR — the post-
sentreceipt recovery this page defers to - Webhook DLQ and recovery — replay for delivery events your endpoint missed, one stage past this page
- Rate limits and cool-downs — the 429 taxonomy behind the quota holds above
- Message status transition rules — the terminology map behind the decoder above