Most CRM texting setups have the same plumbing. A record changes, a workflow fires, an HTTP call sends a text, and later a reply has to find its way back to the right record. Most of the bugs live in the gaps between those steps: a retried workflow that texts someone twice, a reply that lands nowhere, or an opt-out that the CRM never hears about and texts through a week later.
This guide is for the ops person or developer wiring SMS into Zoho, Power Automate, or a homegrown CRM. It covers outbound calls from workflow triggers, inbound replies by webhook, the patterns worth copying, and the per-segment math. The examples use the Ready API, but the structure applies to any provider.
The three moving parts
Every SMS automation has three parts:
- A trigger in your system. A deal moves stage, an appointment is booked, an invoice fails, or a form is submitted.
- An outbound API call. Your workflow tool makes an HTTP request to send the text.
- An inbound path back. Replies and opt-outs come back to you by webhook, or you poll for them, and your CRM updates the record.
Most teams build steps 1 and 2 in an afternoon. Step 3 often gets skipped, and that is how a CRM ends up texting a contact who replied STOP. We cover the inbound side in depth in receiving SMS via API. Here the focus is on how all three fit together.
Outbound: calling the API from a workflow
The send request is small. Ready's base URL is https://api.tryready.com, and you authenticate with an X-API-Key header:
POST https://api.tryready.com/v1/messages
X-API-Key: <your key>
Idempotency-Key: 3f9c1e2a-7b44-4d1e-9a51-0c2f6b8e1d73
Content-Type: application/json
{"to": "+12125550199", "body": "Your appointment is confirmed for Tuesday at 10 AM. Reply C to confirm.", "consent_attested": true} to takes E.164 format, and a 10-digit US number is treated as +1. consent_attested is your promise that this person agreed to receive texts. Ready logs that attestation per send, which tells you who sent, to how many people, and when. It is not a per-contact consent record. The opt-in time, the wording the person agreed to and the page URL belong in your CRM, so keep fields for them on the contact. Optional fields include from (one of your numbers), media_url for an MMS, and from_strategy (local, even or random). Full reference: docs.tryready.com/api/messages.
Zoho CRM
In Zoho, use a workflow rule with a custom function. A Deluge invokeurl call sends the POST shown above, with the headers in a map and the JSON body built from the record's mobile field. Normalize the number to E.164 before the call, because Zoho phone fields hold whatever someone typed. For the Zoho-specific setup, see connecting an SMS API to HubSpot, Zoho, n8n, WordPress and Odoo.
Power Automate
Use the HTTP action with these settings:
- Method: POST
- URI: the
/v1/messagesendpoint - Headers:
X-API-KeyandIdempotency-Key - Body: the JSON shown above
In Power Automate the HTTP action is a premium connector, so check your license before you design around it. Put the API key in an environment variable or Key Vault rather than pasting it into the flow.
The retry trap
Workflow tools retry failed actions, and that is usually what you want. With SMS, though, a timeout after the provider has already accepted the message means a retry sends a second text. The Idempotency-Key header prevents this. Generate one UUID per triggered event, store it on the record, and send that same key on every retry. Ready treats repeated requests with the same key as one send. We go deeper in how to stop duplicate texts from your SMS API.
Inbound: webhooks and the fallback
Ready posts inbound events to a webhook URL you configure. The event types and payload fields are in the webhooks docs. Don't build against field names copied from a blog post. Whatever the payload looks like, a few rules hold:
- Verify the signature first. Every event carries an
X-Signatureheader, an HMAC-SHA256 of the body made with your webhook secret. Recompute it and compare in constant time before you trust the event, or anyone who finds your URL can forge replies and opt-outs into your CRM. The steps are in verify the signature. - Return 200 fast. Put the event on a queue and process it afterward. A webhook handler that runs a CRM lookup and three updates before responding will time out under load.
- Dedupe on your side. Webhook senders retry too, so record which events you've already processed.
- Match on phone number. Normalize to E.164 in both systems. Formatting differences are the most common reason a reply ends up orphaned.
If you can't expose a public endpoint (some locked-down Power Automate tenants can't), polling works. GET /v1/messages returns messages and pages with before_id. Run it every few minutes from a scheduled flow, store the last ID you processed, and start from there on the next run. Webhooks still beat polling on latency, but polling is a reasonable fallback.
Opt-outs need their own sync
Ready honors STOP and UNSUBSCRIBE automatically, and an opted-out number is refused up front at no charge. Your CRM still needs to know, though, so it stops queuing workflows for that contact and so a sales rep doesn't text them from another tool. GET /v1/opt-outs lists opt-outs, and GET /v1/opt-outs/:phone checks a single number. A nightly job that pulls the list and sets a "do not text" flag in the CRM handles most cases, but that list holds only opted-out contacts on this account (a STOP from a deleted contact, a dialer do-not-call entry and a family-account STOP are left out). Checking the single number before a high-stakes send handles the rest.
Automation patterns worth copying
| Trigger | API call | Notes |
|---|---|---|
| Lead form submitted (with SMS consent box) | POST /v1/messages | Send within minutes. Store consent wording and URL on the contact. |
| New lead enters CRM | GET /v1/validate/:phone, then GET /v1/scrub/:phone | Catch landlines and known litigator/DNC numbers before any workflow fires. The standalone scrub is $0.005 per contact. |
| Appointment booked | POST /v1/messages | Quiet hours are enforced. A reminder triggered at night is held only if the window opens within 6 hours; otherwise it is refused, so trigger it in local daytime. |
| Payment failed | POST /v1/messages with a stored idempotency key | Billing systems retry webhooks often, which makes idempotency essential here. |
| Segment of opted-in customers | POST /v1/messages/bulk | Maximum of 100 recipients per call, so batch larger lists. See bulk docs. |
| Reply received | Webhook → your queue → CRM update | Log the reply on the record, move the deal stage, notify the owner. |
On quiet hours: you don't need timezone logic in Zoho or Power Automate to stay within permitted sending times, because Ready enforces them based on the recipient's area (an API text that can't go out within 6 hours is refused, not held). You still need it if you want a reminder to arrive at a particular local time.
Ready no longer lets accounts create AI reply agents. One set up earlier keeps answering texts (the inbound text goes to an AI provider) until you switch off the automation it runs in, or press Take over on a conversation. If you want a reply drafted automatically, that logic runs in your workflow and you decide what gets sent.
Throughput when a workflow fires in bulk
A workflow that triggers on "all contacts in segment X" can produce thousands of sends at once. Carriers meter campaigns per second. A fresh standard campaign without brand vetting runs at roughly 1 to 4 segments per second. These are approximations, since carriers publish no official rate card. At an assumed 4 segments per second, 5,000 single-segment texts take about 21 minutes to go out. Your API calls can finish in seconds while the texts are still draining, so don't treat a 200 response as "delivered." SMS API rate limits explains the caps in more detail.
The cost math
Ready charges $0.0075 per segment plus a $0.0045 carrier fee, sent or received. There is one Standard rate.
Worked example (assumptions stated): an appointment workflow sends 2,000 reminders a month. Each is under 160 plain characters, so it's 1 segment. Assume about 30% of recipients reply with one short text.
- Outbound: 2,000 × ($0.0075 + $0.0045)
- Replies: 600 × ($0.0075 + $0.0045)
- Total: 2,600 segments, which is $52 in Ready's rate plus 2,600 × $0.0045 in carrier fees
Ready's all-in prices per segment include the carrier fee but not screening: Scrub lists and Number validation, the two switches in Settings under Compliance, each add $0.003 per segment when switched on, shown as screening on your bill, and both are off by default. Unless you turn the opt-out footer off, the first text to each contact, and one again from time to time, also gets "Reply STOP to opt out" and your business name if the message does not already have them, so a text close to the segment limit can bill one more segment.
Now add one emoji to that template. A 140-character text with an emoji is 3 segments instead of 1, so the outbound side triples. Check your templates for emoji before you build a workflow around them.
Registration has to be in place before volume. An account can send up to 500 texts a month before it registers. After that it needs 10DLC registration: $25 charged at submit (the first month), then $25/mo for registration + $2 per number. Most approvals take 2 to 7 days. The first 20 one-to-one texts are free, but they end the moment you pay for registration, so use them to test your workflow before you register. To send, you add a card and get a number, which is $2 a month with the first month charged when you get it.
To be straightforward about price: Twilio lists about $0.0083 per segment, which is below our rate. If you have engineers who want to assemble registration, opt-out sync, quiet hours and scrubbing themselves, a raw CPaaS is a reasonable choice. What we include instead is that compliance stack, in-app registration, and an inbox where replies land even if your webhook goes down.
Takeaways before you build
- Generate an idempotency key per triggered event and reuse it on retries.
- Store per-contact consent (time, wording, URL) in the CRM.
consent_attestedis your promise, not the record. - Build the inbound path from day one, either a webhook or polling
GET /v1/messages. - Sync opt-outs back to the CRM on a schedule.
- Batch bulk sends at 100 recipients and expect carrier throughput to set the pace.
- Check templates for emoji before they go out.
For platform-specific setups, SMS integrations for CRMs like Pipedrive and Salesforce covers the rest. When you're ready to test, create an account, send a few of your free one-to-one texts with a single API call (workflow sends are paid from the first text), and confirm the reply comes back to the right record before you switch anything on for real contacts.