IOSOR Learn

Undelivered vs rejected vs expired: a status dictionary for product and billing

A shared status dictionary for B2B teams: what undelivered, rejected, and expired actually mean, who gets charged for each, and how to stop product and finance arguing over the same webhook.

"It failed" is not a status. When product blames engineering, engineering blames the pipe, and finance asks why they were billed for a message nobody received, the root cause is usually the same: nobody agreed on what undelivered, rejected, and expired mean before volume made the disagreement expensive.

IOSOR runs white-label prepaid messaging with one status vocabulary across product, ops, and the wallet — so a status word means the same thing in your dashboard, your webhook payload, and your invoice.

Why status words cause more incidents than outages

A real outage is rare and obvious. What burns a Tuesday afternoon is a support ticket that says "the message failed" when three different systems logged three different terminal states.

  • Product assumes a bug when it was a carrier-side rejection.
  • Finance assumes a refund is owed when the segment was legitimately delivered late.
  • Support promises a fix for something that was never broken — it was mislabeled.

The status dictionary: definitions product and billing can agree on

Status What it means Who typically owns the fix
Accepted / queued Platform took the job; not yet handed to a route Nobody — this is normal
Sent / submitted Handed to the live route Not proof of handset delivery
Delivered Positive terminal confirmation Conversion-grade signal
Undelivered Accepted and sent, but never confirmed delivered before timeout Ops: route or destination health
Rejected Refused before or during transit by network/policy rules Product

Undelivered vs rejected: different failure classes, different fixes

Undelivered usually means the message left your platform correctly but something downstream — a dead handset, a carrier filter, a temporary route problem — swallowed it silently. The fix lives in routing, retries, or destination quality review, not in your application code.

Rejected usually means the message never really had a chance: invalid number format, opt-out on file, blocked sender ID, or a compliance rule tripped before send. The fix lives in your product — validate input, respect suppression lists, fix sender registration — not in a support escalation to "make delivery better."

Expired: TTL, queues, and OTP timing windows

Expired is its own category because it is a time problem, not a delivery problem.

Red flags

  • Only two states exist: "sent" and "failed"
  • Support cannot tell you, on request, which invoice line corresponds to which status
  • "Rejected" and "undelivered" billed identically with no explanation
  • Expired treated as a mystery instead of a TTL configuration question
  • Status definitions that change between the dashboard, the webhook, and the invoice

Start with IOSOR

Map your status callbacks in the IOSOR console so your billing integration cleanly separates early rejections from downstream undelivered events and queue expirations.

Why do some handsets force UCS-2 encoding? · How to troubleshoot high SMS latency? · What are the benchmarks for OTP deliverability?

IOSOR takeaway

This guide demonstrated that status ambiguity is a product design and accounting problem rather than a simple network failure. Distinguishing between carrier rejections, downstream undelivered states, and TTL expirations clarifies financial accountability and stops support teams from hunting ghost bugs in application code.

Do mirror your downstream DLR webhook states directly into your billing ledger to ensure transparent invoice reconciliation. Don't group all delivery drop-offs into a binary sent-or-failed status that obscures whether a message failed due to formatting, network filtering, or expired timing windows.

Was this guide helpful?

Related guides