The three success statuses: delivered vs submitted_no_receipt vs sent
Three outbound statuses can all describe a message that, in the reader’s
mind, “went fine” — sent, submitted_no_receipt, and delivered — yet
they encode three very different grades of success:
delivered— terminal, read-confirmed by the receiver (carrier DLR or read receipt).submitted_no_receipt— an intermediate sentinel promoted by the no-DLR scheduler once the per-channel grace window closes with no receipt.sent— the earliest wire acknowledgment; the message is in flight.
The three buckets — terminal vs wire-intermediate on a promotion boundary
Every status you can read off an outbound row sits on one side of a promotion boundary: a terminal status the receiver (carrier or recipient) asserted, or a wire-intermediate status that names how far the submission travelled but not the outcome.
The row is stamped each transition by the single writer
messages.messages row, and reporting distills the three into two
canonical ladders: numeric delivery rate and event state-class —
see Failure modes for why reducing them blindly poisons
both.
Recasting the three buckets with the reader’s mental model:
- Terminal success says “a receiver asserted the outcome.”
submitted_no_receiptsays “the scheduler bounded the wait; the outcome is still an open variable.”sentsays “the wire ACK came back; the receiver has not spoken yet.”
The promotion rules — the no-DLR safety net, the per-channel grace windows, and the partial index
A background scheduler (the no-DLR transition net in the webhook worker) promotessent rows through a two-step sweep per tenant every 5-minute
tick: a bounded SELECT ... WHERE status = 'sent' AND <channel gate> AND sent_at < NOW() - <grace> (capped, FOR UPDATE SKIP LOCKED) followed
by a second UPDATE ... WHERE id IN (...) RETURNING ... that flips the
row. The relevant gates:
The two-select promotion sweep is bounded by a partial index,
idx_messages_no_dlr_channel(nodlr_channel) WHERE no_dlr_channel = TRUE,
which keys only the TRUE rows (currently Meta DM traffic) so the sweep
cost stays flat regardless of overall message volume. Every row entering
the messages table carries the flag: the sole canonical message writer
(MessagesRepository.create) derives no_dlr_channel = (channel IN ('instagram','messenger')) at the insert seam and binds it explicitly,
so a row either joins the index cleanly or provably stays out of it.
A row never runs from nothing: the sent → <grace> predicate reads
sent_at (not created_at), so a retry that lands late restarts its
window fairly. Per promotion, the random receiver of dashboard parity
hooks (SSE, webhooks, cache-bust, fallback escalation) fans out exactly
like a real DLR flip would. See
the Scheduler section of the cross-check note
below — the schedule is part of the longevity contract, not an
implementation detail you timing-tune against.
Why submitted_no_receipt is a sibling-but-skipped status on every consumer path
Five waypoints read messages.status without advancing it, and each of
them deliberately treats the sentinel differently:
- The canonical writer. The single writer that creates or merges a
messagesrow at send time derives and bindsno_dlr_channel, but never writessubmitted_no_receipt; promotion is the scheduler’s job. - The no-DLR scheduler. The five promotion UPDATEs transition
sent → <sentinel or undelivered>per channel grace, and the reconciliation UPDATE heals a sentinel row back to a genuine receipt. - The DLR receiver path. A genuine late receipt flips the sentinel
back to
delivered/read(weight 4/5 beats 2.5) — a forward-only correction, never a regression. - The data-integrity probe.
status='sent' AND no_dlr_channel=false AND sent_at < NOW()-25hrows flag the gap class; Meta-DM-flagged rows are excluded so a healthy safety net never ring-fences a false positive. - The unified-inbox/receipt planes. The DLR chokepoint and the
scheduler’s parity fan-out emit
message.receipt/message.status_changedframes so the inbox thread and dashboard lists stop freezing at “Sent” or “Submitted (no receipt) once the row converges.
message.failed because no
dedicated event exists — branch on data.status), route trace (GET /api/v1/messages/:id/trace renders the sentinel as its own event with
stage_state intact), budgets (the wallet debit stays where the
send path charged it except the scheduler’s explicit undelivered refund;
the sentinel itself is not a refund trigger), and fallback decisions
(the cross-channel cascade trigger relies on the scheduler’s aged-to-
undelivered flip, not on the sent/ACK origin, which must not be
skipped).
Metadata gate — no_dlr_channel: true (Meta DM) means the sentinel is
a functional delivery (Meta guarantees accept-delivery on opted-in
recipients); false means ordinary honest ambiguity.
Failure modes a reader must not confuse
Two failure modes share the postcard-statussent surface and only one
of them is a bug:
- Low-DLR — a report-capable channel whose carrier should return
receipts but does not (a non-cooperating route, a dropped Jasmin
deliver_smPDU, an endpoint drift on email). The scheduler ages the row toundeliveredand, where a pre-send charge is recorded, frankly reverses it. Track it in your failure KPIs; it is a new-shipment ambiguity class. - No-DLR — a channel (currently Instagram / Messenger, both flagged
no_dlr_channel=true) where by contract no receipt ever arrives. The scheduler promotes the row tosubmitted_no_receipt; reporting counts it on the delivered numerator for exactly those two channels (channel-awarestatus IN (MESSAGE_DELIVERED_STATUSES_UNCONDITIONAL) OR (status = 'submitted_no_receipt' AND channel IN (META_DM_CHANNELS))). Do not confuse the second class with the first: a no-DLR channel is not broken, and a low-DLR channel will not fix itself by being promoted to the sentinel.
channel, and only then
interpret the sentinel. The data-integrity probe’s no_dlr_channel=false
predicate and the unified META_DM_CHANNELS set exist to keep the two
classes apart on the same field.
Related pages
- Delivery lifecycle — every status meaning and who advances it
- Message status map — the four-owner index over the status pages and the transition DAG
- Message status DAG — allowed transitions, weights, and floors
- The DLR model: two planes — which plane each receipt crossed
- Message route-trace — per-message
event timeline including the
submitted_no_receiptevent - Troubleshooting: message sent but no delivery receipt —
when a row sits at
sentor the sentinel with no terminal event