Delivery status and message states
What each textbee message state means, which timestamp it sets, how to track sends with webhooks or the API, and what failure codes mean.
Updated
Every message has a status that shows how far it got. An outgoing message starts as pending, moves to sent when the carrier accepts it, and becomes delivered if the carrier confirms delivery. It ends as failed if the phone cannot send it. Each step also records a timestamp, so you can see where a message waited.
Message states
Statuses are lowercase strings.
| Status | Meaning | Timestamps set |
|---|---|---|
pending | textbee stored the message. It is waiting to be pushed to the phone. Scheduled and paced messages wait here until their time. | requestedAt, and dispatchDueAt for queued messages |
dispatched | The push service accepted the message for the phone. The phone may not have it yet. | dispatchedAt, dispatchAttempts |
sent | The phone reported the message as sent: the carrier accepted it. | sentAt, plus pushReceivedAt and sendAttemptedAt from the phone |
delivered | The carrier sent a delivery report: the recipient's handset got the message. | deliveredAt |
failed | The message could not be sent. This state is final. | failedAt, errorCode, errorMessage |
unknown | textbee has no confirmed state. The phone reported a state textbee cannot map, or sent no report within 20 minutes. | errorMessage when it timed out |
received | An incoming message. Incoming messages always have this status. | receivedAt |
delivery_failed | Rare. The carrier returned a negative delivery report. This value is not in the API reference enum, and it fires the UNKNOWN_STATE webhook event. The message may still have arrived. | None |
A message in unknown can still change. If the phone reports later, the status moves to sent, delivered or failed.
The life of a message
This is what happens between your API call and the recipient's screen:
- Queued on the server. textbee stores the message as
pendingand returns200. A scheduled message, or part of a large batch, waits until itsdispatchDueAttime. - Pushed to the phone. textbee hands the message to the push service, which sets
dispatched. The push service delivers it to the phone when the phone is online. The phone recordspushReceivedAt. - Handed to the radio. The phone waits for its Send Delay gap, then passes the message to Android's SMS system. The phone records
sendAttemptedAt. - Carrier accepted. The carrier accepts the message and the phone reports
sentwithsentAt. - Recipient confirmed. If the carrier returns a delivery report, the phone reports
deliveredwithdeliveredAt.
Why delivered may never arrive
sent is the last state that every message reaches. delivered depends on the carrier. Some carriers do not send delivery reports. Some send them only for some destinations. A report can also arrive hours later if the recipient's phone was off.
So treat sent as success for most purposes. Treat delivered as extra confirmation when it comes. Do not wait for delivered before you mark a message as done.
Batch status
Each send creates a batch. The batch has its own status:
| Status | Meaning |
|---|---|
pending | textbee created the batch and has not queued it yet. |
processing | textbee is handing the messages to the push service, wave by wave. |
completed | Every message was handed off, or every message reported the same final state. |
partial_success | Some messages were handed off and some failed. |
failed | No message could be handed off, or every message failed. |
The batch fields successCount and failureCount count the hand-off to the phone. They do not count carrier results. To see the result for each recipient, read the messages array or filter the message history.
Three ways to track delivery
1. Webhooks
Subscribe to MESSAGE_SENT, MESSAGE_DELIVERED and MESSAGE_FAILED. textbee then sends an HTTP request to your URL each time a message changes state. Each payload has smsId, smsBatchId, recipient and status. This is the best option for most apps, because you do not need to poll.
This request creates a subscription for the three status events:
curl -X POST https://api.textbee.dev/api/v1/webhooks \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Delivery tracking",
"deliveryUrl": "https://example.com/textbee/webhook",
"signingSecret": "a-long-random-secret-of-20-chars-or-more",
"events": ["MESSAGE_SENT", "MESSAGE_DELIVERED", "MESSAGE_FAILED"]
}'Webhook events lists every field of each event, and Webhooks explains signatures and retries.
2. The batch endpoint
GET /gateway/devices/{id}/sms-batch/{smsBatchId} returns the batch and every message in it. Use the smsBatchId from the send response and the ID of the device that sent it.
/api/v1/gateway/devices/{id}/sms-batch/{smsBatchId}, open in the API reference
curl https://api.textbee.dev/api/v1/gateway/devices/664a9b8cd0e1f2a3b4c5d6e7/sms-batch/66b1f2c3a4d5e6f7a8b9c0d2 \
-H "x-api-key: YOUR_API_KEY"3. Message history filters
GET /gateway/messages filters by batch and by status. This call lists the recipients of one batch that failed:
curl "https://api.textbee.dev/api/v1/gateway/messages?smsBatchId=66b1f2c3a4d5e6f7a8b9c0d2&status=failed" \
-H "x-api-key: YOUR_API_KEY"This call lists every failed send on the account, newest first:
curl "https://api.textbee.dev/api/v1/gateway/messages?direction=sent&status=failed" \
-H "x-api-key: YOUR_API_KEY"Message history covers every filter and cursor pagination.
Failure codes
A failed message has an errorCode and an errorMessage. When the phone could not send, errorCode is the Android result code as a numeric string, for example "1". errorMessage explains it in plain words.
errorCode | Android constant | errorMessage (short) | What to do |
|---|---|---|---|
1 | RESULT_ERROR_GENERIC_FAILURE | "SMS failed on device. Common causes: no SMS credit on SIM, weak signal, or carrier blocked." It can end with "(code N)", the radio's own code. | Check the SIM balance and signal, then retry. |
2 | RESULT_ERROR_RADIO_OFF | "Mobile radio is off (e.g. airplane mode)." | Turn off airplane mode and turn on mobile service. |
3 | RESULT_ERROR_NULL_PDU | "Message could not be sent; invalid format or carrier issue." | Check for an empty body or unusual characters. Try a shorter message. |
4 | RESULT_ERROR_NO_SERVICE | "No cellular service." | Move the phone to a place with coverage and retry. |
5 | RESULT_ERROR_LIMIT_EXCEEDED | "Device/carrier send limit reached (too many SMS in a short time)." | Wait a few minutes. Increase Send Delay and spread large batches over time. |
7 | RESULT_ERROR_SHORT_CODE_NOT_ALLOWED | "Short code not allowed on this carrier." | Send to a full phone number. |
8 | RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED | "Short codes are not supported on this carrier." | Send to a full phone number. |
17 | RESULT_NETWORK_ERROR | "Network error while sending." | Check the signal and retry. |
The errorCode value is the number of the Android SmsManager constant. An unlisted number is another Android result, and its errorMessage names the constant.
Codes that start with FCM_ mean the push to the phone failed, so the phone never got the message. Open the app on the phone and reconnect it if needed. Messages not sending covers these and other causes.
Read the timestamps to find a delay
When a message arrives late, compare its timestamps. Fetch the message with GET /gateway/devices/{id}/sms/{smsId} or find it in the message history.
| Gap | What it means | Where to look |
|---|---|---|
requestedAt to dispatchedAt | Time in the server queue. Long gaps are normal for scheduled and paced messages. | Compare with dispatchDueAt. |
dispatchedAt to pushReceivedAt | Time before the phone got the push. The phone was offline or asleep. | Keep the phone online |
pushReceivedAt to sendAttemptedAt | Time the message waited on the phone. Send Delay and earlier messages in the batch cause most of it. | The Send Delay setting and the batch size. |
sendAttemptedAt to sentAt | Time in the radio and the carrier network. | Signal strength and carrier. |
Older app versions do not report pushReceivedAt and sendAttemptedAt. Update the app to see them.
Frequently asked questions
Is sent the same as delivered?
No. sent means the carrier accepted the message. delivered means the carrier reported that the recipient's handset got it. Many messages stay at sent because the carrier sends no report.
Why is my message stuck in pending?
It has not reached the push service yet. Check dispatchDueAt: a scheduled message or a paced batch waits until that time. If it is not queued, check that the device is enabled and online.
Should I retry a failed message?
Retry codes 1, 4 and 17 after you fix the signal or the SIM. Do not retry short code errors. Send a new request: textbee does not resend a failed message by itself.
Does textbee retry a failed webhook?
Yes. A failed webhook delivery is retried. See Webhooks.