Skip to main content

Queued to sent: the four-owner map

You already know the six states an out-of-band queued message moves through. What you usually do not know is where each state’s full story lives — because five different pages each own one slice of the arc, and nothing tells you which one to open for the question you actually have. This page is that map. Resolve your question here, then jump straight to the page that owns the answer, without reading five pages to find one rule. Four of the neighbours are the status cluster: the delivery lifecycle, the message-status map, the message-status transition rules (DAG), and the message route trace. The fifth, send-at scheduled sends, owns the scheduled hold that parks a row before the arc starts. The map below keeps them in traversal order — the order you consume them when you read top-down from “what does the status mean” to “where did my row actually go.”

The per-bucket story — which side of the sweep each state lives on

Every outbound row falls into one of two buckets, and the bucket decides who advances it and who can never touch it: The distinction matters because the forward arc and the outcome classes sit on opposite sides of the sweep: the platform’s schedulers sweep forward rows for work (due scheduled rows to promote, stale sent rows to close) and sweep outcome rows for nothing — a cancelled or expired row is invisible to every drain, which is exactly what “terminal” means. A scheduled row holds the forward arc at queued’s doorstep: it is pre-arc, cancellable, and it joins queued only when its fire time arrives.

The page-chain traversal — five pointers in reading order

A paragraph-level reader following an end-to-end question (for example, “cancellable until fire time, then which merge rule governs the DLR race?”) walks this chain:
  1. Delivery lifecycle — read first. It owns what each status means and which actor advances it. The cancellation reader stops here: cancelled is defined on this page, and the forward arc’s per-transition table names the send pipeline, the DLR pipeline, and the no-DLR scheduler by name.
  2. Message-status map — read second. It owns the row-per-status owner index: the primary and secondary owning page for every status value, so you stop here whenever a webhook payload shows a status you cannot place.
  3. Message-status transition rules (DAG) — read third. It owns the legal-transition graph: precedence weight, merge contract, and floors — the rules you apply when reconciling webhook events into your own datastore.
  4. Send-at scheduled sends — the scheduled branch off the chain. It owns the pre-arc hold: the three exits (promote at fire time, cancel, expire) and how quiet hours and send windows interact with the drain at fire time.
  5. Message route trace — read last, when one specific row needs explaining. It owns the per-message ordered timeline (GET /api/v1/messages/:id/trace) assembled on read from the persisted message row and webhook-delivery log.
A reader who only cares about cancelling a scheduled row stops at step 4. A reader reconciling events stops at step 3. Both are done before the trace page — the chain’s pointers are scoped so you stop the moment the owning page is reached, not when the chain runs out.

The index-sweep vs sentinel distinction — one paragraph to bundle sent and no_dlr_channel

The write-side insert that first stamps a row as sent also stamps the channel flag no_dlr_channel on the same row — and that pairing is the whole story on Meta DM channels (Instagram, Messenger), where the provider never emits delivery receipts. Without one bundling paragraph, a reader meets sent on the delivery-lifecycle page as a wire-intermediate state and assumes a receipt is coming; one row over, on channels flagged no_dlr_channel: true, the same sent row is the best answer the carrier will ever give, and the no-DLR scheduler closes it as submitted_no_receipt after a short per-channel grace window instead of the long SMPP one. Treat the pair (sent, no_dlr_channel) as one predicate: a sent row on a no-DLR channel is a sentinel — an accepted submission awaiting nothing — while a sent row on an SMPP-backed channel (SMS, MMS, voice, fax, RCS) is an index-sweep candidate whose grace window the scheduler is genuinely sweeping. Grouping your submitted_no_receipt rate by no_dlr_channel is what tells apart “the route never reports receipts” from “Meta channels resolved at a five-minute grace” — the two sentences your reconciliation query needs.

The sibling anchors — what each one reads and what leaves

Each anchor below names the slice its page owns, in one scope sentence, so the map is a pointer and never a retry:
  • Delivery lifecycle — reads the state meanings and the advancing actors (send pipeline, DLR pipeline, no-DLR scheduler, operator); leaves the legal-graph merge rules and the per-event vocabulary to the DAG and trace pages.
  • Message-status map — reads the row-per-status owner index and the transition table as a consumer uses it; leaves the per-channel caveats and SMPP receipt encoders to the delivery-lifecycle and send-side-DLR pages.
  • Message-status transition rules (DAG) — reads the legal-transition graph, precedence weight, and the merge contract verbatim; leaves definitions of the statuses themselves to the lifecycle page.
  • Send-at scheduled sends — reads the pre-arc scheduled hold and its three exits (promote, cancel, expire), plus quiet-hours and timezone interaction at fire time; leaves the post-acceptance arc to the lifecycle page.
  • Message route trace — reads the per-message ordered timeline assembled on read from the persisted message row and webhook-delivery log (GET /api/v1/messages/:id/trace); leaves the flat state machine to every page above, and is the diagnostic you open only when one row needs its history reconstructed.
Resolve the question here, open the one anchor, and close the map — that is the whole contract this page makes with the five it points at.