Delivery reports

An SMS delivery status, state by state.

An SMS the API accepted is not yet an SMS received. In between, the recipient's carrier (IAM, Orange or Inwi) returns a delivery report — the DLR. Here is how to read it and what to do with it in your app.

The six states of a message

Every message carries a status, readable with GET /v1/messages/{id} and pushed to your webhook.

  • queued — accepted by EnvoiSMS.ma, waiting to leave.
  • sent — handed to the network; the carrier has not ruled yet.
  • delivered — the carrier confirms it reached the handset.
  • failed — the carrier or the route refused it; error_code says why.
  • undeliverable — the number cannot be reached (does not exist, disconnected).
  • unconfirmed — the network accepted it but never reported an outcome within the reporting window; a late DLR still replaces it.

Where the delivery report comes from

The DLR is issued by the recipient's carrier, not by us: we relay it as it comes. A phone that is off or out of coverage delays delivery, and the DLR with it, sometimes by hours. "sent" is not a failure; it is a wait.

Reading a failure: error_code

A failed or undeliverable message carries a short, stable error_code your code can branch on.

  • DLR_<network status> — the failure came from the carrier's report (e.g. DLR_REJECTD, DLR_EXPIRED, DLR_UNDELIV).
  • GATEWAY_* — a failure on the EnvoiSMS.ma routing side, before the network was reached.
  • META_* — a WhatsApp refusal, for messages sent on that channel.
  • Treat an unknown code as a terminal failure: new values may appear.

Receiving statuses by webhook

Register a URL and subscribe it to message.sent, message.delivered and message.failed. Every call is signed: verify the X-EnvoiSMS-Signature-256 header (HMAC-SHA256 of the timestamp and body) and reject a timestamp older than 5 minutes. The metadata you sent with the message comes back in the webhook, so you can tie the status to your order or user.

What you are billed

A failed message is refunded as soon as we have evidence the network did not charge for it; where that evidence is missing, our team reviews it rather than refunding blind. For a one-time code you generate yourself, set metadata.purpose = "otp": on a hard failure to a Moroccan number, a code that is still valid (under 10 minutes) is re-sent once over another SMS route at no extra charge.

Everything you need to know about Delivery reports.

Why does my SMS stay "sent"?
The carrier has not returned its delivery report yet, often because the phone is off or out of coverage. The status moves to delivered or failed when the DLR arrives, or to unconfirmed if no outcome comes within the reporting window.
Does "delivered" mean the message was read?
No. For SMS, delivered means the carrier handed it to the phone. SMS has no read receipt; WhatsApp does (read).
Am I billed for a failed SMS?
It is refunded as soon as we have evidence the network did not charge for it. Otherwise our team reviews it before deciding.
Should I poll the API or use a webhook?
A webhook: the status is pushed to you the moment it changes, with no polling loop. GET /v1/messages/{id} remains useful for a one-off check or a recovery.