To add texting to your app, you get an API key, register your brand and campaign for 10DLC so carriers don't filter you, and send each message with one authenticated POST request. Then you handle the three things that cause most production problems: replies, opt-outs and retries. This guide covers how to integrate sms api into apps in that order, using Ready's API with real request shapes.

The sending call is the easy part and takes about ten minutes. Registration, opt-out state and duplicate sends take more planning, so most of this post covers them.

What you're building

Before any code, here is the full set of parts. If you're a platform texting on behalf of your own customers, add step 0.

  1. Sub-accounts (platforms only): one per customer, each with its own numbers and its own 10DLC registration.
  2. An API key stored server-side.
  3. A registered brand and campaign, plus at least one number on that campaign.
  4. A send function that calls POST /v1/messages with an Idempotency-Key.
  5. A reply path, using webhooks or polling GET /v1/messages.
  6. Opt-out awareness in your own data model, so your UI knows who can't be texted.
  7. Retry logic that never double-sends.

Ready is a messaging API for software companies, and it also works for anyone who just wants to text their own customers. The same steps apply to a single-business app and to a multi-tenant SaaS product. The only difference is whether step 0 exists.

Step 1: get an API key and keep it off the client

Sign up at tryready.com/signup, create an API key in the app, and put it in your server's secret store. Every request uses one header:

X-API-Key: <your key>

Never ship the key in a mobile app or browser bundle. Anyone who extracts it can send texts that are billed to you. Your client calls your backend, and your backend calls Ready.

A card on file is required to send, and billing is prepaid credits through Stripe. The first 20 texts are free and are one-to-one sends; campaigns are paid from the first text. Use those free texts to test your integration before you register, because they end the moment a registration is paid for.

The quickstart walks through the dashboard side if you want screenshots.

Step 2: register 10DLC before you go to production

US carriers filter unregistered business texting from 10-digit numbers. A2P 10DLC registration tells them who you are (the brand) and what you send (the campaign). In Ready you do this in-app.

Here is what it costs and how long it takes:

ItemCost
Charge at submit (first month)$25
Registration, monthly$25
Each number, monthly$2
Setup feeNone
Typical approvalMost approvals take 2 to 7 days

While you wait, you aren't blocked. An account can send up to 500 texts a month before it registers, at its normal per-text rate. After that it has to register. An account that bought its number before registering sends on Ready's shared registration at the same per-text rate while its own registration is in review.

You can check status from code with GET /v1/registration. This is useful for gating a "texting enabled" flag in your app's settings page.

Two registration details catch developers out:

  • Pick the use case that matches what you actually send. Registering "marketing" when you mostly send reminders, or the reverse, causes trouble later.
  • Your sample messages should look like your real traffic. Reviewers compare them.

If you're a platform registering many customers' brands, the 10DLC registration API guide covers the multi-tenant flow. Create each customer's sub-account with POST /v1/sub-accounts and list them with GET /v1/sub-accounts. Buy numbers with POST /v1/numbers. Each sub-account carries its own numbers and registration, so one customer's bad list doesn't drag down another's sending.

Step 3: send your first text with POST /v1/messages

Here is the whole call:

curl -X POST https://api.tryready.com/v1/messages \
  -H "X-API-Key: $READY_API_KEY" \
  -H "Idempotency-Key: 3f1c2a9e-8b4d-4e5f-9a1b-7c6d5e4f3a2b" \
  -H "Content-Type: application/json" \
  -d '{"to": "+12125550199", "body": "Your order #1042 has shipped.", "consent_attested": true}'

What each field does:

  • to: an E.164 number. A 10-digit US number is treated as +1, but normalize to E.164 in your own database anyway.
  • body: the text.
  • consent_attested: your promise that the recipient agreed to receive texts. Ready records the sender's attestation per bulk send and per API send that sets consent_attested: who sent it, to how many people and when. Ready does not store a per-contact consent record. Your app should keep the opt-in time, wording and page URL for each contact, because that is your evidence if a complaint comes in. (A contact who signs up on a Ready-hosted opt-in page is the exception: Ready stores that sign-up, with the time, IP address and exact wording, and support@tryready.com can send you a copy.)

There are also three optional fields:

  • from: one of your numbers, if you want a specific sender.
  • media_url: a picture, which makes the message an MMS.
  • from_strategy: how Ready picks among several numbers, only needed if you hold more than one. See the messages reference.

For one message going to many numbers, use POST /v1/messages/bulk with to as an array. The bulk messages docs cover limits. For a full working Node example with fetch, see how to send SMS with Node.js. Field-level details are in the messages API docs.

What it costs per message: worked example

Ready bills per segment at $0.0075/segment, plus a $0.0045 carrier fee per segment itemized separately. That's the Standard rate, and it is flat: it doesn't change with volume.

Segments matter more than message count. A plain GSM-7 text fits 160 characters in one segment, and longer ones split into 153-character parts. One emoji drops the limit to 70 characters (67 per part). Ready swaps curly quotes, long dashes and ellipses for plain ones before sending, so those don't silently change your count.

Example assumptions: an app sends 10,000 shipping notifications a month, and each one is a 120-character plain-text message.

  • 120 GSM-7 characters = 1 segment
  • 10,000 × 1 × $0.0075 = $75 in per-segment charges
  • Plus 10,000 × $0.0045 in carrier fees

Now add one emoji to the same template and make it 140 characters. That is 3 unicode segments, so the per-segment charge becomes 30,000 × $0.0075 = $225, plus 30,000 × $0.0045. One emoji triples the bill. Count segments in your template editor, not just characters.

These figures exclude the compliance surcharge. Litigator/DNC scrubbing and landline validation each add $0.003 per segment when switched on for an account, and both are off by default.

A note on price: raw CPaaS APIs are lower per segment. Twilio lists around $0.0083/segment. If you only need a pipe to the carriers and you're happy to build registration, opt-out state, quiet hours and scrubbing yourself, a raw CPaaS is a reasonable choice. The case for Ready is what is included: self-serve registration, sub-accounts, STOP handling, quiet hours and scrubbing that you don't have to build.

Step 4: handle replies

Texting is two-way whether you plan for it or not. People will reply "what time?" to your reminder.

You have two ways to get replies into your app:

  • Webhooks. Ready can notify your server when messages arrive. Event names and payloads are in the webhooks docs, so take them from there rather than guessing.
  • Polling. GET /v1/messages returns messages and pages with before_id. This works well for a backfill job, or as a safety net that reconciles anything a webhook missed during an outage.

A sensible pattern is to use webhooks for real time, plus a nightly reconcile with GET /v1/messages that upserts anything you don't have yet.

Inbound replies are billed like a sent text: the per-segment rate plus the carrier fee. If your product invites replies, include that in your cost model. Replies also show up in the Ready inbox, and in GoHighLevel for connected accounts, so your support team can answer them without you building an inbox. The two-way SMS API post covers threading replies to the right record.

Step 5: opt-outs, quiet hours and what gets refused

You don't have to build STOP handling. When a recipient replies STOP or UNSUBSCRIBE, Ready records the opt-out, and that contact can't be messaged again across campaigns.

Your app should still know about opt-outs so your UI doesn't offer to text someone who can't receive it:

  • GET /v1/opt-outs lists the opted-out contacts on this account. It leaves out a STOP from a deleted contact, a dialer do-not-call entry and a STOP held on another account of the same family, so it is not a complete mirror.
  • GET /v1/opt-outs/:phone checks a single number. Call it before showing a "Send text" button, or cache the result.

The API protects you in a few other ways:

  • Opt-outs, numbers on your Ban list and known landlines are refused up front and not charged.
  • Quiet hours: quiet hours are based on the recipient's area. An API text is held only if sending reopens within 6 hours; otherwise the API answers 409 QUIET_HOURS_BLOCK with the reopen time and nothing is queued, so your app has to send it again. A held text is charged only when it actually sends. Don't build your own "is it 9 PM in Ohio" logic on top of this.
  • Validation: GET /v1/validate/:phone tells you whether a number is mobile, landline or disconnected before you store it as textable.
  • Scrubbing: GET /v1/scrub/:phone checks a number against known TCPA-litigator and DNC-complainer lists. The standalone scrub costs $0.005 per contact. TCPA exposure runs $500 to $1,500 per text, so a cheap check on imported lists is worth it.

None of this makes you lawsuit-proof. Compliance remains the sender's responsibility. These tools reduce risk; they don't remove it. Text people who opted in or who already replied to you. Cold outreach belongs on the phone or in the mail, not in your SMS integration.

Step 6: retries without duplicate texts

Networks fail. Your request times out, you don't know whether the text went out, and you retry. Without protection, the customer gets "Your order shipped" twice. With a payment reminder, a duplicate is worse than annoying.

The fix is the Idempotency-Key header:

  • Generate one UUID per logical message, for example per "shipping notice for order 1042". Don't generate one per HTTP attempt.
  • Store it on your outbound message record before you call the API.
  • Reuse the same key on every retry of that message. Ready won't send twice.
  • Retry with backoff on timeouts and 5xx errors. Don't retry 4xx errors; fix the request instead.

Throughput is another reason requests can seem slow. A fresh standard campaign without vetting runs at roughly 1 to 4 segments per second. At an assumed 2 segments per second, 10,000 single-segment texts take about 83 minutes to go out. That is the carriers' limit, not a bug in your code. Queue sends on your side and don't treat slow delivery as a failure to retry. The idempotency post has a full retry loop.

Pre-launch checklist

  • API key in a server-side secret store, never in the client
  • Brand and campaign registered, with GET /v1/registration gating your "texting enabled" flag
  • Per-contact consent (time, wording, page URL) stored in your database
  • consent_attested: true sent only when that record exists
  • Segment counter in your template editor, with emoji warnings
  • Webhook handler, plus a reconcile job using GET /v1/messages
  • Each number checked with GET /v1/opt-outs/:phone before a send button shows
  • One Idempotency-Key per logical message, reused on retries
  • A send queue that respects carrier throughput

If you want to try it, sign up, send your free one-to-one texts to your own phone, wire up the reply path, and register when the flow works end to end. Pricing is on the pricing page. For enterprise volumes, talk to us.

Frequently asked questions

How long does it take to add SMS to an app?

The sending code takes under an hour, but production texting depends on 10DLC approval. Most approvals take 2 to 7 days. Meanwhile, an account can send up to 500 texts a month before it registers.

Do I need 10DLC registration to send texts from my app?

Yes, for ongoing business texting from 10-digit US numbers. Carriers filter unregistered traffic. In Ready, registration has no setup fee: $25 is charged at submit for the first month, and after that the monthly is $25 for registration and $2 per number.

How much does an SMS API cost per text?

On Ready, it's $0.0075/segment plus a $0.0045 carrier fee per segment, with replies billed the same way. Scrubbing and landline validation each add $0.003 per segment if you switch them on. Raw CPaaS APIs like Twilio are lower per segment but leave registration and compliance tooling to you.

What happens if my API request times out and I retry?

If you send the same Idempotency-Key on the retry, the text won't be sent twice. Generate one key per logical message, store it, and reuse it on every attempt. A new key tells the API this is a new message.

Can my app text people who haven't opted in?

No. SMS should go only to contacts who consented or who already replied to you. Ready records your attestation per bulk send and per API send that sets consent_attested, but your app has to store each contact's opt-in record, and compliance stays your responsibility.