> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: message sent but no delivery receipt

> Work through why a message is holding at sent or submitted_no_receipt with no delivered or undelivered outcome — late carrier receipts, per-channel grace windows, and how to tell a delayed DLR from an unreachable recipient.

# Troubleshooting: message sent but no delivery receipt

Your message left the queue and the provider accepted it — the Delivery
Log shows `sent` 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](/reference/troubleshooting) first —
this page covers only the post-queue stage.

## The status ladder: sent is not an outcome

Once a message reaches `sent`, the provider accepted it on the wire. From
there the only forward moves are:

```text theme={null}
sent → delivered → read
```

and the failure branches (`undelivered`, `failed`, `rejected`,
`expired`). Two properties of this stage drive most confusion:

* **`sent` is 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
  carries `state_class: "intermediate"` and `is_terminal: false`.
* **`submitted_no_receipt` is also intermediate.** When no receipt
  arrives inside the per-channel grace window, Orbit promotes the row
  from `sent` to `submitted_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 to `delivered` (or a failure status)
  and a fresh webhook fires. This is the `submitted_no_receipt` → later
  `delivered` reconciliation flow documented in the
  [webhook events reference](/reference/webhook-events).

The full per-status semantics live in the
[message status lifecycle reference](/api-reference/messages-status-lifecycle);
the concept-level walkthrough is the
[delivery lifecycle page](/concepts/delivery-lifecycle).

<Note>
  `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)](/reference/webhook-events#expired-and-submitted_no_receipt-delivered-via-messagefailed).
</Note>

## 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 the `delivered` status indefinitely while the
  platform-side row stays at `sent`.
* **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_receipt` after 5 minutes. For an opted-in recipient,
  Meta guarantees delivery on accept — treat this variant as
  functionally delivered. The message metadata carries
  `no_dlr_channel: true` so you can distinguish this case from the
  ambiguous SMPP one.

Receipts that arrive after the grace window still reconcile into the
row and fire a webhook, up to the platform's late-arrival window
(`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](/reference/webhook-events) 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:

1. **Read the row by API.** `GET /api/v1/messages/:id` returns the
   authoritative `status`. If it is still `sent`, the grace window has
   not closed yet; `submitted_no_receipt` means it has.
2. **Look for a later event.** A subsequent `message.delivered` (or a
   `message.failed` with a terminal status like `undelivered`) supersedes
   the earlier `submitted_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.
3. **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.
4. **Compare with the destination's history.** A high
   `submitted_no_receipt` share 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_receipt` inside that window is the normal
  sentinel, not a stalled send.
* If the row flips to `delivered` minutes 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_receipt`
  rate, the carrier route is not returning receipts. Track it as a
  separate KPI from `delivered` and 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.

**WhatsApp**

* 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 `delivered`
  callback while Meta still accepts the send.
* Watch `message.failed` for Meta's error payload (`error_code`,
  `error_message`) — a bounced template or policy refusal surfaces
  there, not as a missing receipt.

**Email**

* Allow hours, not minutes: recipient mail servers queue and can bounce
  well after acceptance. The `bounced` status arrives on the same
  asynchronous webhook path as delivery.
* A `sent` email that later reports `bounced` is 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 at `sent` 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_receipt` for 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.

Include both of these so support 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/me` response as
  `organizationId`).
* The **message ID** of one stuck row (`msg_…`).

That pair lets support pull the exact DLR pipeline trace for the row
and the provider-side receipt state without re-describing the symptom.

## See also

* [Troubleshooting: message stuck in queued](/reference/troubleshooting)
  — the pre-queue stage, for rows that never reached a provider
* [Webhook events reference](/reference/webhook-events) — payload shape
  for every lifecycle event, including the `is_terminal` field and the
  `submitted_no_receipt` event mapping
* [Delivery lifecycle](/concepts/delivery-lifecycle) — the concept-level
  state machine and the carrier-confirmed vs. wire-intermediate
  distinction
* [Message status lifecycle](/api-reference/messages-status-lifecycle)
  — per-status semantics and the full transition table
