Troubleshooting: message sent but no delivery receipt
Your message left the queue and the provider accepted it — the Delivery Log showssent or submitted_no_receipt — but neither delivered nor
undelivered ever lands. This page walks you through what that state
means, how long each channel takes to return a delivery receipt (DLR),
and how to tell a delayed receipt from a recipient who is genuinely
unreachable.
If the message has not left the queue at all (queued, scheduled,
pending), work the
queue-side troubleshooting page first —
this page covers only the post-queue stage.
The status ladder: sent is not an outcome
Once a message reachessent, the provider accepted it on the wire. From
there the only forward moves are:
undelivered, failed, rejected,
expired). Two properties of this stage drive most confusion:
sentis intermediate, not an outcome. A delivery receipt must still come back from the carrier or provider. Until it does, the message has no final answer — every lifecycle webhook for this stage carriesstate_class: "intermediate"andis_terminal: false.submitted_no_receiptis also intermediate. When no receipt arrives inside the per-channel grace window, Orbit promotes the row fromsenttosubmitted_no_receipt. That is a sentinel meaning “submission accepted; no receipt yet — outcome unknown,” not a successful delivery and not a failure. A late receipt can still land and overwrite it: the row flips todelivered(or a failure status) and a fresh webhook fires. This is thesubmitted_no_receipt→ laterdeliveredreconciliation flow documented in the webhook events reference.
submitted_no_receipt arrives on the message.failed event type
because there is no dedicated event for it — branch on the payload’s
status field, not the event type, and keep it out of your hard-failure
metrics. See
expired and submitted_no_receipt (delivered via message.failed).Where receipts get delayed per channel
A delayed receipt almost always means the receipt path is slow or dropped somewhere outside the platform. Where the delay sits differs by channel:- SMS (and MMS, voice, fax, RCS) — the carrier’s SMSC runs a store-and-forward protocol. Submission is acknowledged immediately, but the DLR travels back later: carriers batch receipts, and some routes only emit one when the handset moves or polls. The grace window is 30 minutes; a receipt can arrive long after that and still reconcile, up to the late-arrival window. Some destinations and routes never emit receipts at all.
- WhatsApp — Meta pushes status callbacks (
sent→delivered→read) to Orbit in near real time, but Meta-side delays, a recipient who blocked you, or a recipient with no active WhatsApp account can hold thedeliveredstatus indefinitely while the platform-side row stays atsent. - Email — the provider reports delivery and bounces asynchronously via webhooks. A recipient mail server can hold the message in queue and bounce hours later, so no bounce in the first minutes is not evidence of delivery.
- Meta DM channels (Instagram, Messenger) — Meta’s Send API never
emits delivery receipts, so on these channels the row always resolves
to
submitted_no_receiptafter 5 minutes. For an opted-in recipient, Meta guarantees delivery on accept — treat this variant as functionally delivered. The message metadata carriesno_dlr_channel: trueso you can distinguish this case from the ambiguous SMPP one.
DEVOTEL_DLR_LATE_ARRIVAL_MAX_AGE_DAYS). A receipt that lands beyond
that window cannot be reconciled; the row resolves to expired. See
the late-arrival notes in the
webhook events reference for the
reconciliation behaviour.
Distinguish “receipt missing” from “recipient unreachable”
The intermediate statuses tell you no receipt arrived. They do not, on their own, tell you whether the recipient got the message. Check these in order:- Read the row by API.
GET /api/v1/messages/:idreturns the authoritativestatus. If it is stillsent, the grace window has not closed yet;submitted_no_receiptmeans it has. - Look for a later event. A subsequent
message.delivered(or amessage.failedwith a terminal status likeundelivered) supersedes the earliersubmitted_no_receipt. If you mirror statuses into your own datastore, apply updates by message ID — a late receipt arriving after you logged the sentinel is normal. - Check the Provider Reference column. In the Delivery Log, open the row and read the Provider Reference column. Populated means the provider handed back a wire-level ID for the submission. When you contact a carrier or escalate, that reference is what the provider traces against.
- Compare with the destination’s history. A high
submitted_no_receiptshare on SMS concentrated in one destination country or route points at a non-cooperating route; the same share spread across every destination points at a receipt-path problem.
Channel checklists
Work the list for the channel that produced the stuck row. SMS / MMS / RCS- Wait past the 30-minute grace window before diagnosing — the row
moving to
submitted_no_receiptinside that window is the normal sentinel, not a stalled send. - If the row flips to
deliveredminutes or hours later, that is a late-carrier receipt reconciling; treat the destination as slow-DLR, not broken. - If the same destination shows a persistent
submitted_no_receiptrate, the carrier route is not returning receipts. Track it as a separate KPI fromdeliveredand route through a different sender pool if delivery confirmation matters for the use case. - If many destinations all flip at once, suspect the receipt path (a provider edge or webhook interruption) rather than the carriers — escalate.
- Confirm the template you sent is still approved — Meta pauses templates on quality and policy status changes.
- Check the recipient side: a blocked business number or a recipient
without a WhatsApp account leaves the row without a
deliveredcallback while Meta still accepts the send. - Watch
message.failedfor Meta’s error payload (error_code,error_message) — a bounced template or policy refusal surfaces there, not as a missing receipt.
- Allow hours, not minutes: recipient mail servers queue and can bounce
well after acceptance. The
bouncedstatus arrives on the same asynchronous webhook path as delivery. - A
sentemail that later reportsbouncedis a recipient-side reject — handle it as a separate outcome from provider-side failures. - If no delivery and no bounce ever appears, verify your webhook endpoint subscription covers the email channel’s events before assuming the provider is at fault.
What not to do
Do not retry or resend while the row is atsent or
submitted_no_receipt. Retry acts on terminal failures; a
still-intermediate row can turn delivered after a late receipt, and a
second copy you sent in the meantime reaches the recipient twice.
Likewise, do not count submitted_no_receipt rows in your failure
metrics — the outcome is unknown, and overwriting the tracker when the
late receipt lands is expected behaviour.
When to escalate
Open a support ticket when one of these holds:- A row sits at
submitted_no_receiptfor more than 24 hours with no terminal event. - A wide spread of destinations (not one recipient or one route) stopped returning receipts at the same time.
- You need the provider-side trace: include the Provider Reference from the Delivery Log row alongside the IDs below.
- 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_…).
See also
- Troubleshooting: message stuck in queued — the pre-queue stage, for rows that never reached a provider
- Webhook events reference — payload shape
for every lifecycle event, including the
is_terminalfield and thesubmitted_no_receiptevent mapping - Delivery lifecycle — the concept-level state machine and the carrier-confirmed vs. wire-intermediate distinction
- Message status lifecycle — per-status semantics and the full transition table