123eworld Knowledge Hub → Transactional SMS → Page 109
Transactional SMS API Message Lifecycle State Machine: Queued, Submitted, Delivered, Failed and Expired
A developer reference guide to designing a reliable SMS message lifecycle state machine, covering accepted, queued, processing, submitted, delivered, failed, expired and uncertain states, legal transitions, duplicate events and historical evidence.
Why explicit states matter
A single status column without transition rules quickly becomes ambiguous. Developers need to know which statuses are temporary, terminal or uncertain and what events are allowed to move a message between them.
Accepted versus queued
Accepted means the API has validated and durably recorded the request. Queued means work has been handed to the asynchronous processing system. These states may be close together but should not be conflated if the architecture needs to diagnose queue publication failures.
Processing
A processing state indicates that a worker has claimed the message. It should have a recovery mechanism so a crashed worker does not leave the message permanently stuck.
Submitted
Submitted should mean the provider accepted the message request according to the provider's response contract. It does not mean the handset received the SMS.
Delivered
Delivered should be based on trustworthy provider delivery evidence. Do not infer delivery merely because submission succeeded.
Failed
Failed should include a categorized reason and identify whether the failure occurred during validation, queueing, provider submission or delivery.
Expired
Time-sensitive messages may become useless after a deadline. Expiration should be an explicit transition so workers do not send stale notifications.
Unknown or uncertain
A provider timeout can create uncertainty rather than a confirmed failure. Preserve an uncertain state until reconciliation or a safe provider mechanism resolves the outcome.
Terminal states
Define which states are terminal and whether a terminal state can be superseded by later provider evidence. Avoid arbitrary updates from asynchronous callbacks.
Out-of-order events
A DELIVERED callback arriving after a duplicate FAILED callback should be evaluated according to provider event timestamps and internal transition rules, not simply accepted in arrival order.
Audit history
Store lifecycle events separately from current status where historical reconstruction matters. This lets operators understand how and why the final state was reached.
Implementation checklist
Define states, legal transitions, ownership of each transition, uncertainty handling, expiry, callback ordering, terminal rules and lifecycle-event history.
State transition ownership
Each transition should have a clear owner. The API owns acceptance, the queue publisher owns durable handoff, workers own provider submission states and the callback processor owns delivery evidence.
This prevents unrelated services from overwriting status fields casually.
Atomic transitions
Where concurrent workers or callbacks can update the same message, use atomic state transitions or optimistic concurrency. A callback should not overwrite a newer terminal state simply because it arrived later.
State reason codes
Pair broad statuses with structured reason codes. FAILED is useful for reporting, but INVALID_RECIPIENT, PROVIDER_REJECTED and EXPIRED are far more useful for troubleshooting.
State timestamps
Record timestamps for major lifecycle events separately. A single updated_at field cannot explain how long a message spent queued, processing or awaiting delivery.
Uncertain state handling
Uncertainty should be explicit. If a provider request timed out after the network connection was established, record that the outcome is unknown and prevent an automatic second submission until the configured reconciliation policy permits it.
Cancellation
Cancellation should have clear semantics. A message may be cancellable while queued but no longer cancellable after provider acceptance. The API should return the current state rather than pretending cancellation succeeded.
Lifecycle testing
Build a transition matrix and test every allowed and forbidden transition. Include duplicate callbacks, late callbacks, worker crashes, retries, expiry and cancellation races.
Production scenario
A worker submits a message and times out. The message enters an uncertain state. A delayed provider callback later reports DELIVERED. The state machine accepts the evidence and records both the uncertain submission event and final delivery event, preserving the complete timeline.
Terminal-state protection
Once a message reaches a terminal state, later events should be evaluated against explicit rules. For example, a late FAILED callback should not overwrite a previously verified DELIVERED state unless provider semantics explicitly require reconciliation.
Lifecycle event schema
A lifecycle event can contain event ID, message ID, old state, new state, reason code, actor or service, provider reference and timestamps. This creates an audit trail without forcing every query to reconstruct history from application logs.
State reporting
Customer-facing APIs should expose stable statuses while internal systems retain richer state and reason information. This keeps the public contract understandable while preserving diagnostic detail.
State-machine documentation
Publish a transition diagram and examples in developer documentation. Integrators should understand that submitted does not equal delivered and that uncertain outcomes may require reconciliation.
Developer rule
Never treat a status string as self-explanatory. Define its meaning, legal transitions and evidence source.
Lifecycle security
Only trusted internal services and authenticated providers should be able to cause state transitions. Clients should not be able to set delivered, failed or submitted statuses directly.
Lifecycle metrics
Track time spent in each state and count messages stuck beyond expected thresholds. A growing processing-state age can reveal worker crashes even when overall queue depth looks normal.
Lifecycle acceptance test
Execute every legal transition, attempt every forbidden transition and inject duplicate and out-of-order provider events. Verify that the final state and event history remain consistent.
Reference rule
Current state tells you where the message is; lifecycle history explains how it got there.
State machine and APIs
The public API should expose a stable subset of lifecycle states while internal processing retains richer states. For example, internal states may distinguish QUEUED, PROCESSING and SUBMISSION_UNKNOWN while the customer-facing API groups them under an appropriate processing category.
State machine and reconciliation
Reconciliation jobs should be allowed to update uncertain states using provider evidence. They should use the same transition rules as normal callbacks rather than writing directly to the status column.
State machine and retention
Lifecycle events may need longer retention than operational logs because they can support billing disputes, delivery investigations and compliance evidence. Define retention separately from log retention.
Failure-mode walkthrough
A message is submitted successfully, but the provider response is lost. The platform records submission uncertainty. Later, a callback reports delivered. The state machine accepts the delivery evidence, records the callback and leaves the original uncertainty event intact. A separate retry worker cannot create a duplicate because uncertainty is explicitly handled rather than treated as failure.
Architecture checklist
Confirm legal transitions, transition ownership, concurrency controls, reason codes, timestamps, uncertainty, expiry, cancellation, callback ordering, event history and reconciliation.
State transition matrix
Create a table showing every source state, event, permitted destination state, evidence required and actor responsible. For example, QUEUED may transition to PROCESSING after a worker claim, while DELIVERED should not return to QUEUED because of a late callback.
Practical implementation
Represent lifecycle rules in code as explicit transition logic rather than scattered status assignments. Centralizing transitions makes it easier to test forbidden updates and to change provider mappings without introducing accidental state corruption.
Final lifecycle scenario
A message progresses from accepted to queued to processing, then encounters a provider timeout. It becomes uncertain rather than failed. A later authenticated callback reports delivered, so the state machine records the evidence and reaches the terminal delivered state. Every transition remains visible in the lifecycle event history.
Final implementation note
Keep state transitions atomic and test late events explicitly.
Operational acceptance
During acceptance testing, generate duplicate callbacks, delayed callbacks and competing worker updates. Verify that only legal transitions are committed, that reason codes and timestamps remain accurate and that the final status reflects the strongest available evidence. Then replay the same event stream to prove the lifecycle processor is deterministic and idempotent.
Final engineering note
Document every status and transition in the API reference.
Reference summary
A message lifecycle state machine provides a shared language between API developers, workers, provider adapters, support teams and customers. Explicit states, legal transitions, reason codes, timestamps and evidence sources prevent contradictory status updates and make asynchronous behaviour understandable.
Closing rule
Make every transition explicit.
Quality standard
Late events must never corrupt newer evidence.
Need transactional SMS integration?
123eworld.com provides Bulk SMS and API-based business communication solutions for enterprises and software applications.