textbee Logotextbee.dev
Save 44% with yearly billing.View Plans
Documentation

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.

StatusMeaningTimestamps set
pendingtextbee 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
dispatchedThe push service accepted the message for the phone. The phone may not have it yet.dispatchedAt, dispatchAttempts
sentThe phone reported the message as sent: the carrier accepted it.sentAt, plus pushReceivedAt and sendAttemptedAt from the phone
deliveredThe carrier sent a delivery report: the recipient's handset got the message.deliveredAt
failedThe message could not be sent. This state is final.failedAt, errorCode, errorMessage
unknowntextbee has no confirmed state. The phone reported a state textbee cannot map, or sent no report within 20 minutes.errorMessage when it timed out
receivedAn incoming message. Incoming messages always have this status.receivedAt
delivery_failedRare. 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:

  1. Queued on the server. textbee stores the message as pending and returns 200. A scheduled message, or part of a large batch, waits until its dispatchDueAt time.
  2. 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 records pushReceivedAt.
  3. 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.
  4. Carrier accepted. The carrier accepts the message and the phone reports sent with sentAt.
  5. Recipient confirmed. If the carrier returns a delivery report, the phone reports delivered with deliveredAt.

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:

StatusMeaning
pendingtextbee created the batch and has not queued it yet.
processingtextbee is handing the messages to the push service, wave by wave.
completedEvery message was handed off, or every message reported the same final state.
partial_successSome messages were handed off and some failed.
failedNo 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:

Shell
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.

GET/api/v1/gateway/devices/{id}/sms-batch/{smsBatchId}, open in the API reference
Shell
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:

Shell
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:

Shell
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.

errorCodeAndroid constanterrorMessage (short)What to do
1RESULT_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.
2RESULT_ERROR_RADIO_OFF"Mobile radio is off (e.g. airplane mode)."Turn off airplane mode and turn on mobile service.
3RESULT_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.
4RESULT_ERROR_NO_SERVICE"No cellular service."Move the phone to a place with coverage and retry.
5RESULT_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.
7RESULT_ERROR_SHORT_CODE_NOT_ALLOWED"Short code not allowed on this carrier."Send to a full phone number.
8RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED"Short codes are not supported on this carrier."Send to a full phone number.
17RESULT_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.

GapWhat it meansWhere to look
requestedAt to dispatchedAtTime in the server queue. Long gaps are normal for scheduled and paced messages.Compare with dispatchDueAt.
dispatchedAt to pushReceivedAtTime before the phone got the push. The phone was offline or asleep.Keep the phone online
pushReceivedAt to sendAttemptedAtTime 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 sentAtTime 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.

Next steps