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

Sending SMS

Send an SMS with POST /gateway/send-sms: request fields, response, status codes, phone number format, segments and error handling.

Updated

To send an SMS, call POST /gateway/send-sms with a message and a recipients array. textbee pushes the message to your Android phone, and the phone sends it through its SIM. The response returns an smsBatchId that you use to follow delivery.

POST/api/v1/gateway/send-sms, open in the API reference

Before you start

  • A textbee account with a verified email.
  • A registered device that is online.
  • An API key, sent in the x-api-key header.

Request fields

Send a JSON body. Only message and recipients are required.

FieldTypeRequiredDescription
messagestringYesThe text of the message. Long text is split into segments.
recipientsstring[]YesPhone numbers in E.164 format. Each recipient counts as one message against your plan.
deviceIdstringNoThe phone that sends. Without it, textbee uses your default device, or else the enabled device with the most recent heartbeat.
simSubscriptionIdnumberNoThe SIM that sends, on a dual-SIM phone. Without it, the phone uses its default SIM. The ID can change after a SIM swap, so copy the current one from the textbee app. See Send from a specific SIM.
scheduledAtstringNoAn ISO 8601 time with a timezone, up to 72 hours ahead. Without it, the message goes now. See Schedule a message.

Send a message

This sample sends one message from your default device. It reads the key from the TEXTBEE_API_KEY environment variable:

send.sh
BASE_URL="${TEXTBEE_BASE_URL:-https://api.textbee.dev/api/v1}"

curl -sS -X POST "$BASE_URL/gateway/send-sms" \
  -H "x-api-key: $TEXTBEE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipients": ["+12015550123"], "message": "Hello from textbee"}'

Working in JavaScript or TypeScript? The @textbee/sdk package wraps this call with types and error classes.

Send from a specific device

This variant passes deviceId to choose the phone. Find the ID in the dashboard Devices panel or in GET /gateway/devices:

Shell
curl -X POST https://api.textbee.dev/api/v1/gateway/send-sms \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipients": ["+12015550123", "+12015550124"],
    "message": "Your appointment is tomorrow at 10am",
    "deviceId": "664a9b8cd0e1f2a3b4c5d6e7"
  }'

A malformed deviceId returns 400 with Invalid device id. An ID that is not on your account returns 404 with Device not found. textbee never falls back to another phone when you pass deviceId.

Response

A successful send returns 200:

JSON
{
  "data": {
    "success": true,
    "message": "SMS added to queue for processing",
    "smsBatchId": "66b1f2c3a4d5e6f7a8b9c0d2",
    "recipientCount": 2
  }
}
FieldDescription
successtrue when textbee accepted the batch.
messageA short outcome in words.
smsBatchIdThe batch ID. Use it to follow delivery.
recipientCountThe number of recipients in the batch.
estimatedCompletionAtOnly on large batches. The time the phone should finish, based on its Send Delay setting. The phone must stay online for the estimate to hold.

Status codes

CodeMeaning
200textbee accepted the message. This is not delivery.
400No enabled device to send from, the device is disabled, your email is not verified, or the message could not be pushed to the phone. The body says which.
401The API key is missing, invalid or revoked.
429Your daily, monthly or per-batch plan limit is used up. The body says which one. Nothing was sent.

What happens after 200

The 200 response means textbee stored the message and queued it for your phone. The message has the status pending. textbee then pushes it to the phone. The phone hands it to the radio and reports sent when the carrier accepts it. If the carrier sends a delivery report, the status changes to delivered. If the phone is offline, the message waits until the phone comes back. Delivery status and message states explains each state and three ways to track it.

Phone number format

Write every number in E.164 format: a plus sign, the country code, then the number, with no spaces or punctuation.

CorrectIncorrect
+120155501232015550123 (no country code)
+12015550124(201) 555-0124 (punctuation)
+12015550125001 201 555 0125 (international prefix instead of +)

textbee removes spaces from each number. It does not add a missing country code. The phone passes the number to its carrier as written. The JavaScript SDK has isValidE164 and normalizePhoneNumber helpers to check numbers before you send.

Segments and encoding

The phone splits a long message into segments and sends each one. The recipient's phone joins them again. The segment size depends on the characters in the message:

EncodingWhen it appliesOne messageEach segment of a longer message
GSM-7Only characters from the GSM-7 alphabet160 characters153 characters
UCS-2At least one other character70 characters67 characters

One character outside GSM-7 switches the whole message to UCS-2. An emoji or a curly quote is enough. A 150-character message with one emoji needs three segments instead of one.

The JavaScript SDK has utilities for this: countSmsSegments, getSmsEncoding, findNonGsm7Characters and sanitizeForGsm7. They run locally and need no API key.

Handling errors

Check the status code of every response. Retry a 429 once after a pause, and do not retry 400 or 401 until you fix the cause. This sample shows the pattern:

errors.sh
BASE_URL="${TEXTBEE_BASE_URL:-https://api.textbee.dev/api/v1}"

# --retry repeats only on transient statuses such as 429; a 400 or 401 fails at once.
STATUS=$(curl -sS -o response.json -w '%{http_code}' --retry 1 --retry-delay 2 -X POST "$BASE_URL/gateway/send-sms" \
  -H "x-api-key: $TEXTBEE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipients": ["+12015550123"], "message": "Hello from textbee"}')

case "$STATUS" in
  200) jq -r '.data.smsBatchId' response.json ;;
  401) echo "API key rejected: $(jq -r .message response.json)" >&2; exit 1 ;;
  400) echo "Request rejected: $(jq -r .message response.json)" >&2; exit 1 ;;
  *) echo "HTTP $STATUS: $(jq -r .message response.json)" >&2; exit 1 ;;
esac

Error bodies carry a human-readable reason. A 429 body has a message field that names the limit. Most 400 bodies have an error field, for example:

JSON
{ "success": false, "error": "No enabled device found. Enable a device or pass a deviceId." }

Frequently asked questions

Does a 200 response mean the message was delivered?

No. It means textbee accepted the message. Follow the status with a webhook or the batch endpoint. See Delivery status and message states.

How many recipients can one request have?

Your plan's per-batch limit sets the maximum. A request over the limit returns 429 and sends nothing. For more numbers, split them into several requests. The pricing page lists the limits.

Can I send different text to each recipient?

Not with this endpoint. Use POST /gateway/send-bulk-sms, where each item has its own text and recipients.

Can I send MMS or images?

No. textbee sends text SMS only.

Why does the message go out from the wrong phone?

Without deviceId, textbee uses your default device. Set the default in the dashboard, or pass deviceId on each request.

Next steps