Skip to main content

SMS Messaging

An Erdo agent can send outbound SMS on your behalf through Erdo’s managed number — the same Erdo-managed model as Voice Calls. No account or carrier setup to connect; just ask the agent to text someone.
“Text +1 415 555 0142 to confirm their demo on Thursday at 2pm.”
Like voice calls, every send is approval-gated (you confirm it in chat first) and requires a consent basis — a concrete reason the message is permitted (explicit opt-in, an existing business relationship, an internal test).

Opt-out is enforced

Recipients who reply STOP are added to your organization’s suppression list, and any further send to that number is blocked before it leaves — the agent is told the recipient opted out and no credit is charged. (STOP is also enforced at the carrier level, so opt-out is honored even on the very first reply.) Suppression is per-organization.

Texts Erdo starts say how to opt out

When Erdo starts a conversation by text, it tells the person how to stop. This covers a follow-up to a lead, an agent’s send_sms and an outreach batch. Erdo adds “Reply STOP to opt out.” to the first text it sends a number from each of your numbers, and records that the person was told. US carriers expect that line on the first text after somebody opts in. Texts to Canadian numbers carry it every time, with your business’s legal name from its business profile: “Reply STOP to opt out of texts from .” Canada’s anti-spam law asks every commercial message to name its sender and give a way to unsubscribe. Replies to somebody who texted you first are sent as written, because they started the conversation. A draft that already ends with the line is not given it twice. Outreach batches end every text with the line, as before. Erdo doesn’t add your business name to outreach texts to Canada, so write it into the batch message yourself.

Sending is rate-limited

Erdo caps how many messages can go out so a loop or a mistake can’t spam anyone: there’s a per-recipient limit (you can’t blast one number) and a per-organization limit over a rolling window. A send that would exceed either is blocked before it leaves and not charged — the agent is told to back off. A logical send can also carry an idempotency key so a retry never sends the same message twice. Calling hours are shared with Voice — configure them once and both channels respect them — but the opt-out list, consent, and rate limits here are tracked per channel: opting out of SMS does not by itself suppress voice calls to that number, and vice versa. To stop all outreach to a recipient, suppress them on each channel you use.

What the agent does

The send_sms capability:
  • Validates the number, body, and consent basis.
  • Checks the opt-out list and your credit balance first.
  • Sends from Erdo’s number and records the message.
  • Charges a small flat credit cost per message, only after the message is accepted (a blocked or opted-out send is never charged).
Two capabilities write to someone on WhatsApp from one of your own WhatsApp numbers. When you have none and the organization that manages you lends you its shared WhatsApp number, they send from that instead, as your lead follow-up does, and the approval card shows that number. There is no shared Erdo number on WhatsApp. They are separate because WhatsApp itself treats the two kinds of message differently:
  • send_whatsapp_reply sends the agent’s own words. WhatsApp delivers these only within 24 hours of the person’s last message to that number. Outside that, the reply is refused before any approval card, and the agent is told to use a template instead.
  • send_whatsapp_template sends a template Meta has approved, with its variables filled in. This is how the agent reaches someone who has not written in the last 24 hours, such as a new lead. The approval card shows the approved wording as the person will read it, in their language. A template Meta has not approved yet is refused before any card.
Each capability sends exactly what the agent asked for; neither switches one kind of message for the other. Opt-outs (WhatsApp’s Stop, or STOP to a text), quiet hours and your credit balance are checked as for a text, and a message is charged only once it is accepted. An Argentine mobile is written to on WhatsApp with its mobile 9, because that is the only form WhatsApp knows it by: a lead stored as +54 11 5555 0142 gets the message at +54 9 11 5555 0142, and their reply from that number lands in the same conversation. The lead keeps the number it was captured with. A Stop sent from either spelling stops both. If the person has already written to you from a number without the 9, Erdo answers on that number unchanged.

Lead outreach

SMS pairs with Voice for lead outreach loops: text a list, capture replies, and let Erdo qualify and follow up. Combined with datasets and workstreams, a whole list can be worked end-to-end.

Incoming texts

A voice agent with its own phone number receives texts on that number as well as calls, and Erdo keeps every one of them. Texts are grouped into an SMS conversation: one Erdo number and one person, kept for as long as they keep texting, rather than a pile of separate messages. A conversation records who wrote and when, every message body and its delivery status, and how many pictures or files were attached. A summary and the topics covered arrive with the reply feature. Someone who texts STOP (or UNSUBSCRIBE, CANCEL, END, QUIT, OPTOUT) is added to your suppression list exactly as if they had replied to an outbound send, and their conversation shows as suppressed. START, UNSTOP or YES lifts it again — from outreach too, if the STOP was a reply to an outreach send. From someone who never opted out, a “Yes” is just a reply, and your agent answers it like any other message. On WhatsApp, “cancel” and “end” are ordinary messages too; only STOP (or UNSUBSCRIBE, QUIT, OPTOUT) opts someone out there. Erdo never writes those confirmations itself — Twilio sends them at the carrier level, which is why opt-out is honored even before Erdo has seen a single message from that person. A block you placed by hand is not lifted by someone texting START: only their own STOP is. The conversation’s suppressed flag always reflects your suppression list as it stands — a block placed before the person ever texted shows on their conversation from its first message.

A texter becomes a lead

Whoever texts your agent’s number reaches you from a number their carrier already verified, so a text conversation becomes a lead in the same table your landing pages and your widget write to — nothing to wire up. Once the conversation goes quiet, Erdo reads it, takes whatever the person said about themselves, and saves the lead with source = sms_incoming and modality = text, alongside the conversation it came from so you can read the whole exchange. The number they texted from is the lead’s phone unless they asked to be reached on a different one — so an enquiry with no name attached is still a lead, exactly as a phone call is. A person who is already a lead is not duplicated: the text conversation fills in what your form left blank and changes nothing you already have. A text that is only an opt-out or opt-in keyword (STOP, START) is not an enquiry and is never saved as a lead. Whether this happens is the Save leads setting on the agent whose number was texted, the same switch that governs the callers who ring it (Agents → Deployments, or lead_capture_enabled on the agent), including which dataset the leads land in. Turn it off and texts are still captured and readable; nothing is written as a lead. Turning it back on starts from the next conversation rather than writing months of old ones. Lead saving starts from the day it ships, too: conversations your number had before then stay readable conversations and are never turned into leads retroactively, so nothing lands in your dataset — or fires a notification — for an enquiry you have already dealt with. Someone texting STOP stops Erdo texting them. It does not delete a lead already saved — you still have the enquiry they made, you simply cannot text them about it.

The agent answers

Your agent replies to a text from its own number, with the same persona and the same knowledge it uses on a phone call. Nothing to switch on: an agent that has a number answers what arrives on it. It writes like a person texting. Plain text, no markdown or bullet lists, no dashes, usually under 300 characters, and at most one question at a time. It never promises that somebody will call back at a particular time, because it cannot make that happen. It qualifies before it asks for a name. The question a reply asks goes first to what your team needs to know: the person’s budget and their timeline, or the qualifying questions your agent’s instructions name instead. It never sends somebody who is texting you to a form. It asks for their name later, once, and never if it already knows it. It knows when you just called. If you phoned the number in the last hour and this is the person’s first text since, often their phone’s automatic “can’t talk” reply, the agent opens by saying who it is and that you just tried to call about their enquiry, and asks nothing else. It is honest and steps back. Asked “who is this?” or “is this a bot?”, it says in one line that it is your business’s virtual assistant and what it can help with. Asked for a person, it says it is passing the request to your team and that someone will get back to them on this number, then stops asking questions. Three texts in ten seconds get one reply. People text in fragments, so your agent waits a few seconds for the person to finish before it answers, then covers everything they said in one message rather than firing back three disconnected replies. A reply normally arrives within seconds of that pause; if something inside Erdo goes wrong on the way, it re-checks every few minutes and answers then, so a text is not left waiting for the person to write again — and no conversation can crowd out another, however long any one of them has been stuck. Replying is on the person’s terms, for a day. For 24 hours after somebody texts you, your agent answers whenever they write — quiet hours do not apply, because they wrote to you and are waiting. A conversation is capped at 30 messages per rolling 24 hours — counted over the last day at every moment, not reset on the hour — which bounds a loop or a flood without anybody having to notice it; if a real conversation hits the cap, it lands in your work feed so you can pick it up yourself. Outside that day, a message from Erdo is your own initiative again and the ordinary calling hours and frequency limits apply. Your organization also has a daily ceiling on how many messages it can send in total. If replies stop because you have reached it, Erdo says so in your work feed rather than going quiet, and the texts waiting are answered as the ceiling rolls forward. STOP always wins. The 24-hour posture relaxes quiet hours and a frequency cap, both of which are about Erdo’s own manners. It never touches opt-out: a person who has texted STOP is never texted again until they say START. Each reply costs credits, at the same flat per-message rate as an outbound SMS, charged only after the carrier accepts it.

Before a US number can reply

US carriers require a local (10-digit) number to be registered before it can send text messages — a one-time registration of your business, not something you do per message. Until a US number is registered, your agent reads texts but cannot reply. Everything is captured and readable in the meantime, and the moment registration is approved the next text is answered with nothing else to switch on. If a text arrives at an unregistered number, Erdo puts an item in your work feed naming the number and pointing at the Business settings page where you register it. This applies to US and Canadian long codes, which share the same carrier registry. A number in another country replies as soon as it is provisioned, and a US toll-free number is verified through its own process rather than this one. If your number moves to a different Twilio account, its approval does not move with it — the registration belongs to the account that holds the campaign — so the number needs registering again before it can reply.

Turning it off

Ask in chat — “stop my Ana agent answering texts” — or use the switch on Agents → Deployments, beside the agent’s number. Texts still arrive and are still readable; the agent just does not write back. The switch is per agent, and an update to anything else about that agent leaves it alone. Outside the app it is POST /v1/voice/agents/{slug}/sms-replies with {"enabled": false}, erdo voice agents sms-replies <slug> --off, or the MCP tool erdo_set_voice_agent_sms_replies (voice toolset; see Toolsets).
WhatsApp is in early access and is switched on per organization — ask us if you would like it. Once it is on, WhatsApp answers on the same number, as its own conversation with its own opt-out — a person who texts you and a person who messages you on WhatsApp are two threads, because they are two separate permissions. RCS comes after that. Outbound SMS the agent starts itself still sends from Erdo’s shared number; only a reply comes from the agent’s own.
Replying will also need the number itself to be cleared by US carriers, which is a one-off form and an approval rather than anything technical: see Registering a Number for Texts. Receiving is unaffected — a number captures texts from the day it exists, registered or not.

Your WhatsApp profile

Once WhatsApp is connected on Settings → Business, the WhatsApp section has a WhatsApp profile card. It sets what people see about your business on WhatsApp: One profile applies to every WhatsApp number in your organization. Saving sends it to each number straight away, and it shows within a few minutes — these fields are not reviewed. A number you register for WhatsApp later starts with the profile already on it. If a number does not accept the update, the card lists it with the reason; your changes are kept, and saving again retries. Upload a PNG, JPG or WebP picture. WhatsApp shows it in a circle, so a square logo with some space around it works best. Erdo fits it into a 640×640 square and puts a transparent background on white, because WhatsApp accepts neither a non-square picture nor transparency. The name people see is not on this card. It is set when each number is registered for WhatsApp, and WhatsApp reviews it; changing it later is a request to us rather than an edit.

Message templates

WhatsApp lets a business send any message to someone who wrote to it in the last 24 hours. To write to someone first — a new lead who filled in your website form, say — it only allows a template: a fixed message Meta has reviewed and approved, with a few blanks like the person’s name. Erdo writes these templates and installs them on your WhatsApp account for you, within about ten minutes of your number going live. The WhatsApp section of Settings → Business lists them under Message templates, with where each one stands with Meta: Each is installed in English, Spanish and Portuguese (Brazil), and each language is reviewed on its own. Erdo writes to a lead in their language; until that language is approved, it uses the English version. Tapping Yes, please is an ordinary reply: your agent answers it, and the conversation carries on normally. A lead who doesn’t want to hear from you can reply STOP, which opts them out of WhatsApp messages from your number. (Earlier copies of this message also had a Stop messages button; tapping it still works the same way as replying STOP.) WhatsApp limits how many people you can write to first each day: 250 until your business is verified by Meta, and more once it is (2,000, then higher as you send). Erdo follows the limit Meta reports for your number. Replies to people who wrote to you do not count.

Reading conversations

Ask in chat — “has anyone texted my number?”, “what did they say?” — or read them directly. From the CLI:
Over the API:
  • List — GET /v1/voice/sms-conversations (or a POST with a JSON body) returns each conversation’s session id, the two numbers, when it opened, when each side last wrote, how many texts it holds, whether the person is suppressed, and — once the conversation has been quiet on both sides for half an hour — its summary, topics and contact. The summary keeps up with the conversation: a reply Erdo sends after one has already been summarised is read into the next summary rather than leaving a stale one until the person texts again. Narrow with phone_number (one of your Erdo numbers) or address (the person); both accept any format and are compared on digits. Narrow with canonical_lead_id to read one lead’s conversations (see below); filters combine. When a response includes next_cursor, pass it back as cursor for the next stable page, with the same filters. MCP tool: erdo_voice_sms_conversation_list.
  • Read one — GET /v1/voice/sms-conversations/{session_id}, using a session id from the list, returns the conversation plus its texts oldest first, so the exchange reads in order. MCP tool: erdo_voice_sms_conversation_get.

Delivery times

Each outbound text carries delivered_at: when the carrier confirmed it reached the person. It is set by the first delivery report and never changes after, so you can use it as the time a text was delivered — for example, as proof that a lead’s number works. It is absent on inbound texts and on outbound ones that have not been confirmed, including texts the carrier reported as undelivered. Each conversation in the list and in a single read also carries first_delivered_outbound_at and first_delivered_outbound_message_id: the earliest confirmed delivery in that conversation and the text it belongs to, absent while nothing has been delivered. WhatsApp messages carry the same delivered_at, plus read_at when the person opened the message. A message that was read counts as delivered even if its delivery report never arrived. WhatsApp conversations carry the same two first_delivered_outbound_* fields. The time is when Erdo received the provider’s report, usually seconds after the send for a text. For WhatsApp it can be hours later, because the report only arrives once the person’s phone comes online. Messages delivered before these fields existed carry the time of the last report Erdo stored; for a WhatsApp message that had already been read, that is the read time, so its delivered_at is the latest it could have been delivered. Media stays at the carrier: a conversation reports how many attachments a text carried, and Erdo does not serve the files themselves. When your leads dataset uses lead identity, each conversation also names the lead it became: canonical_lead_id is that lead’s permanent UUID and lead_reference is the same id in 22 characters, the form a link or a text message carries. Join on it when you show a lead’s texts, rather than on the number — the same person can text from one number and later leave an email that their lead is keyed on. Pass either form as canonical_lead_id on the list to read one lead’s conversations; it narrows your organization’s conversations and never reaches anyone else’s. The same filter works on phone calls and widget conversations. A conversation Erdo starts is named the same way, from the other direction. When a text is sent about a lead you already have — a follow-up your playbook decided on — the conversation is linked to that lead as it is sent, so it appears in the lead’s texts alongside the ones they began. A conversation that already names a lead keeps that one: what the person said about themselves is never replaced by what a send assumed. A text conversation can become a lead more than once — each time the person comes back and says something new about themselves, that episode is written too — so the id is the lead the most recent write resolved to. An episode that resolves none (lead saving was switched off, or the dataset is not enrolled) leaves an earlier id in place rather than erasing it. Both fields are empty while no write has resolved a lead, when the leads dataset is not enrolled in lead identity, and on conversations recorded before conversations carried the lead’s identity; those older conversations keep empty fields rather than being matched to a lead after the fact.

Reading WhatsApp conversations

WhatsApp conversations are read on their own, beside texts rather than mixed into them. They are a separate thread with a separate opt-out, and a WhatsApp message can be things a text cannot: a shared location, a tap on a quick-reply button, a reply quoting an earlier message, or the first message from someone who clicked one of your ads. From the CLI:
Over the API:
  • List — GET /v1/voice/whatsapp-conversations (or a POST with a JSON body) takes the same filters as the SMS list — phone_number, address, canonical_lead_id, limit and cursor — where address finds an Argentine mobile with or without its 9 — and returns the same fields, plus display_name: the WhatsApp profile name the person chose, from their most recent message. suppressed is true while the person has opted out of WhatsApp with you; it says nothing about whether they still accept texts. MCP tool: erdo_voice_whatsapp_conversation_list.
  • Read one — GET /v1/voice/whatsapp-conversations/{session_id} returns the conversation plus its messages oldest first. Beside the body, direction, status and attachment count, each message carries its message_type (text, location, button, voice, …) and the profile_name the person had at the time, and — only when present — location (a shared pin’s latitude, longitude, label and address; the message body is empty), context (the message it quoted or the button it tapped) and referral (the ad the person came in from). A session id from the SMS list is not found here. MCP tool: erdo_voice_whatsapp_conversation_get.
A WhatsApp conversation names its lead the same way a text conversation does, as canonical_lead_id and lead_reference, so a lead’s timeline can show its texts and its WhatsApp messages side by side by reading both lists with the same filter.