What Is a Text Message API? How It Works, With Code

Table of Contents

A text message API is a web interface that lets your software send and receive SMS with an HTTP request instead of a phone or a dashboard. You post the recipient’s number, the text and a sender ID to an endpoint, and the provider routes the message to the mobile operator and later reports whether it was delivered.

This guide explains what happens between your request and the handset, then shows a working curl request and a Node.js example against the SMS.to SMS API, and finishes with how delivery reports reach your server through callback_url. Every example uses the real endpoint POST https://api.sms.to/sms/send, a JSON body and a bearer token, so you can run it as soon as you have a key.

How does a text message API work?

From your side it is one request. Behind it, several systems take part:

  1. Your application sends an HTTPS POST with the number, the message and the sender. The API authenticates the key, validates the number and checks your balance.
  2. The provider works out the encoding and part count, applies opt-out rules, picks a route for the destination network, and replies at once with a message ID. At this point the message is queued, not delivered.
  3. The route hands the message to the operator’s message centre, usually over SMPP, the protocol operators and aggregators use between themselves.
  4. The operator looks up where the handset is and delivers it, or stores it and retries if the phone is off.
  5. A delivery report travels back the same way, and the provider posts the new status to your callback URL.

Two consequences matter in practice. First, an HTTP 200 means “accepted for sending”, never “delivered”. Second, the status you get depends on the network: some operators confirm delivery to the handset within seconds, others only confirm hand-off to their network.

What do you need before your first request?

  • An API key. Create an account, copy the key from the dashboard, and keep it on your server. Send it as Authorization: Bearer YOUR_API_KEY.
  • A recipient in international format. Use E.164: a plus sign, the country code and the national number without its leading zero, up to 15 digits in total, as set out in ITU-T Recommendation E.164. For example, +447700900123.
  • A sender ID. The name or number the recipient sees. Rules differ by country, and many require registration for business traffic.
  • A message that fits. Plain Latin text uses the GSM-7 alphabet and fits 160 characters in one SMS, as defined in 3GPP TS 23.038. One emoji or non-Latin character switches the whole message to Unicode with a 70-character limit, and longer messages are split into billable parts. The SMS length calculator shows the part count before you send.

How do you send an SMS with curl?

This request sends one message and asks for status updates at your callback URL. Replace the number with your own mobile to test it.

curl -X POST https://api.sms.to/sms/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Your order has shipped and will arrive tomorrow.",
    "to": "+447700900123",
    "sender_id": "SMSto",
    "callback_url": "https://example.com/sms/dlr"
  }'

A successful call returns HTTP 200 with a body like this:

{
  "message": "Message is queued for sending! Please check report for update",
  "success": true,
  "message_id": "11ec-832f-a6f3fcfe-9fea-02420a0002ab"
}

Store the message_id. It is the key that links this request to the delivery reports you receive later and to the entry in your message log. If success is false or the status code is not 200, the message field says why: an invalid key returns 401, a validation problem returns 422 with details of the failing field, and too many requests in a short time returns 429.

What does the same request look like in Node.js?

Node.js 18 and later ship with fetch, so this example has no dependencies. Put your key in the SMSTO_API_KEY environment variable rather than in the source file.

// send.js - Node.js 18+ (uses the built-in fetch). Run: node send.js
const API_KEY = process.env.SMSTO_API_KEY || "YOUR_API_KEY";

async function sendSms(to, message) {
  const res = await fetch("https://api.sms.to/sms/send", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      message,
      to,
      sender_id: "SMSto",
      callback_url: "https://example.com/sms/dlr",
    }),
  });

  const body = await res.json().catch(() => ({}));
  if (!res.ok || body.success !== true) {
    throw new Error(`SMS.to returned ${res.status}: ${body.message || "unknown error"}`);
  }
  return body.message_id; // save this to match delivery reports later
}

sendSms("+447700900123", "Your order has shipped and will arrive tomorrow.")
  .then((id) => console.log("Queued with message_id", id))
  .catch((err) => console.error(err.message));

In production you would call sendSms from the place where the event happens, such as an order being dispatched or a login needing a passcode, and save the returned ID alongside that order or login attempt. The to field also accepts an array of up to 2,000 numbers for a one-to-many send; in that case the response contains a campaign_id instead of a message_id.

How do delivery reports (callback_url) work?

A delivery report is a status update the network sends after your request has been accepted. SMS.to forwards each one to a webhook on your server. You can set the URL per request with callback_url, as in the examples above, or once for the whole account as the Default Outbound Callback URL under Webhook Management in your account settings.

According to the SMS.to callback documentation, each update arrives as an HTTP POST. It is form-encoded by default; if your callback URL ends with #json, the body is sent as JSON instead. A failed message looks like this:

{
  "trackingId": "449-1754482-56f7-6f94-2068609b-ece73",
  "messageId": "449-1754482-0250-c360-9ca0f728-4e3d6",
  "phone": "+447700900123",
  "status": "FAILED",
  "type": "messaging",
  "channel": "sms",
  "parts": 1,
  "price": 0.044,
  "errorCode": "2",
  "errorMessage": "Unknown Base Station"
}

The fields you will use most are messageId (to match your stored ID), status, parts and price. Failed messages also carry errorCode and errorMessage. The main statuses, as defined in SMS.to’s status guide, are:

StatusWhat it means
SENTSubmitted to the network; no confirmation from the handset yet. On some networks this is the final status you will see.
DELIVEREDThe network confirmed delivery to the handset.
FAILEDDelivery failed; check errorCode for the reason.
EXPIREDSent to the network but no final report arrived, so the outcome is unknown.
REJECTEDNot sent for a technical reason such as an invalid number format. Not billed.
UNSUBSCRIBEDNot sent because the recipient opted out. Not billed.
NO_FUNDSNot sent because the account balance was too low.

Your endpoint must return HTTP 200 within 3 seconds. If it does not, SMS.to marks the callback as failed and retries after 1 hour, then 2, 4 and 24 hours after each further failure, and then discards it. So acknowledge first, then process: write the payload to a queue or table and return 200 straight away. Callbacks are not instant either, so a late callback does not mean the message was sent late; the message log holds the accurate timestamps.

What else can a text message API do?

Sending one message is the starting point. The same API covers the rest of a messaging workflow:

TaskHow it works in the SMS.to API
Send one text to many numbersPOST /sms/send with to as an array (up to 2,000 numbers)
Send a different text to each numberPOST /sms/send with a messages array of message and to pairs
Schedule for laterAdd scheduled_for and timezone to the request
Check cost and part count firstPOST /sms/estimate with the same body
Look up a message’s statusGET https://api.sms.to/message/ followed by the message ID
Manage opt-outsInclude STOPSMS {optout} in the text, or call the opt-out endpoint

Receiving replies needs a rented number; inbound messages then arrive at a separate inbound callback URL. For high, sustained volume some teams move to SMPP, and our comparison of SMPP API vs HTTP API explains when that is worth it.

How SMS.to handles this

SMS.to is operated by Intergo Telecom, a licensed telecom and CPaaS operator that has been building messaging infrastructure since 2014. The SMS API gives you smart routing with delivery receipts, webhooks for DLRs and replies, and local sender ID options where the destination allows them. Pricing is pay as you go from $0.023 per SMS, varying by country, with volume discounts and free credits when you sign up; see the price list by country. If a destination needs a registered sender ID, the team will guide you on the setup before you launch. Full field-by-field details live in the API documentation, and our guide to your first SMS API integration covers planning the rollout.

FAQs

Is a text message API the same as an SMS gateway?

Mostly. The gateway is the platform that connects to mobile networks; the API is the interface your code uses to reach it. Providers often use the terms interchangeably.

Does an HTTP 200 response mean my SMS was delivered?

No. It means the message was accepted and queued. Delivery is confirmed later by a delivery report sent to your callback URL.

How long can a text message be?

One SMS holds 160 GSM-7 characters or 70 Unicode characters. Longer messages are split into parts of 153 or 67 characters and billed per part.

Can I receive replies through the API?

Yes, if you rent a number that can receive SMS. Replies are posted to your inbound callback URL. Alphanumeric sender IDs cannot receive replies.

The quickest way to understand a text message API is to watch your first delivery report arrive. Create a free account, no credit card needed, and run the curl example above with your own number.

author avatar
Marios Italos

More Articles

A text message API lets your software send SMS with a single HTTP request. Here
Most "free SMS APIs" are free to try, not free to run. This guide explains