Accepted is not delivered
A successful SMS API response usually means the platform accepted or queued the request. It does not prove the message reached the recipient phone. Delivery status arrives later through provider events, carrier receipts, status callbacks, delivery reports, or reconciliation jobs.
That distinction matters for OTPs, account alerts, payment notices, and security messages. If the product treats accepted as delivered, support will see a clean application event while the user still has no message.
A clean status model protects the user experience. Accepted can mean the product should show a neutral waiting state. Sent can mean the message has left the platform but still needs downstream evidence. Delivered can unlock a less urgent support response, while failed, expired, or unknown should lead to retry, fallback, or escalation rules. If those states are collapsed into one boolean, the product cannot explain what happened.
A practical SMS status model
| State | What it means | Product behavior |
|---|---|---|
| accepted | The platform accepted the request. | Store the message ID and wait for later delivery evidence. |
| queued | The message is waiting for processing, route selection, rate limits, or scheduled send time. | Keep the user flow open or show neutral waiting copy. |
| sent | The message left the platform toward a downstream provider or carrier. | Do not mark a critical OTP complete yet. |
| delivered | A provider or carrier signal says delivery succeeded. | Treat as the best available delivery signal, not proof that a human read it. |
| failed or undelivered | The provider or downstream route could not deliver the message. | Show retry or fallback paths based on use case and error category. |
| expired or unknown | The delivery window ended or no reliable final signal arrived. | Avoid blind retries; preserve evidence for support. |
The model should be conservative on purpose. Delivery receipts are useful operational signals, but they are not the same as a human reading the message. Some receipts arrive late, some arrive out of order, and some routes provide less detail than others. For critical flows, keep the state machine honest: store the best available signal, preserve uncertainty, and avoid wording that promises more than the delivery data can prove.
Build the timeline around transitions
Store every status transition with timestamps and raw event references. Do not overwrite the only record of what happened. A final support answer often depends on the sequence, not just the current state.
Out-of-order events are normal enough that the handler should expect them. A delivered callback may arrive after the product already marked the OTP expired. A retry may produce a second provider message ID for the same user action. A duplicate webhook may be delivered minutes after the first copy. The safest timeline keeps each event, applies deterministic precedence rules, and lets support see why the current user-facing state was chosen.
- Keep acceptedAt, sentAt, deliveredAt, failedAt, expiredAt, and lastWebhookAt separate when possible.
- Store provider message ID and internal message ID together.
- Mark which states are terminal for OTP, account alert, and delivery notification flows.
- Let support see stale, duplicate, and out-of-order events without changing the user-facing state incorrectly.
Track delivery states clearly
Use Notilify to model accepted, sent, delivered, failed, expired, and unknown SMS states for support-ready workflows.
Read webhook guide