Skip to content

Message lifecycle

Every SMS submitted to Lisoloo walks the same status machine. You can observe it via GET /status/{message_id} (polling) or via webhooks (push). The status values are stable across releases — branch on them in your code without worrying about renames.

The values below come directly from the backend ELisolooSmsStatus enum. Treat anything in the Terminal column as a final state for that message (or per-recipient, for the fine-grained breakdown).

StatusTerminal?Meaning
pendingNoThe gateway accepted the request and queued it. No carrier handoff yet.
scheduledNoThe message is queued for a future scheduled_dates / recurring_schedule occurrence.
processingNoThe carrier connector picked up the job and is dispatching to the SMSC.
acceptedNoThe SMSC accepted the message; operator-side processing in progress.
enrouteNoThe operator network is delivering to the handset.
sentNoThe carrier confirmed dispatch. Awaiting handset acknowledgement.
deliveredYesThe handset acknowledged receipt.
partially_deliveredYesMulti-segment message — some segments delivered, some did not.
delivery_unknownYesThe carrier did not return a definitive receipt within the window.
undeliveredYesThe carrier reported the message as undelivered (handset unreachable, blocked, etc.).
rejectedYesThe carrier rejected the message before dispatch (typically: invalid sender ID or content).
expiredYesThe message validity window passed without delivery.
failedYesA processing failure inside the gateway or connector.
unknownStatus could not be determined.
noneInternal sentinel value; should not appear on a real message.

delivered is the only fully successful terminal state. Everything else in the Yes column is a non-success terminal state with different operator-level causes.

┌──────────┐
submit ───────▶ │ pending │
└─────┬────┘
│ carrier picks up
┌────────────┐
│ processing │
└─────┬──────┘
│ SMSC accepts
┌──────────┐
│ sent │
└─────┬────┘
│ handset ACK │ timeout / NACK
▼ ▼
┌────────────┐ ┌──────────┐
│ delivered │ │ failed │
└────────────┘ └──────────┘
terminal terminal

A message can transition straight to failed from any earlier state if something goes wrong — for example, a malformed MSISDN that the carrier rejects before SMSC handoff jumps pending → failed without ever passing through processing.

A single POST /send with N recipients produces one message_id that covers the whole batch. The status returned reflects the aggregate:

  • pending while any recipient is pending and none are terminal.
  • processing while some are mid-flight.
  • sent when all recipients have handed off.
  • delivered when every recipient is delivered.
  • failed when every recipient is failed.
  • Mixed terminal states (some delivered, some failed) report delivered if any succeeded, failed if none did.

For per-recipient breakdown, the GET /status/{message_id} response includes a recipients[] array with the individual final state for each number. (See the API reference for the schema.)

Each status transition emits a webhook event to your configured webhook_url (if set) and to any per-request callback_url. See Webhook event types for the payload shapes.

The events are:

  • sms.queued — fired on pending (right after POST /send returns).
  • sms.processing — fired on pending → processing.
  • sms.sent — fired on processing → sent.
  • sms.delivered — fired on sent → delivered.
  • sms.failed — fired on any transition to failed.

sms.delivered and sms.failed are terminal — no further events for that message_id after they fire.

The gateway holds an in-flight message for up to 24 hours waiting for a final delivery receipt from the carrier. If none arrives by then the status is set to failed with error_code: 1503 SMS_TIMEOUT and no further retries are attempted.

Carrier-level retries (typically 3 attempts over 4 hours for transient failures) happen transparently before the gateway gives up. You will not see retry attempts as separate webhook events.