Concept · Iterable · Two-way SMS
Two-way SMS Keyword Router
The plumbing behind the reply keywords in the STEM Camp Lifecycle atlas, built on the auto-response SMS system I ran at iD Tech. Every reply lands in Iterable as an smsReceived event; the work is deciding which journey it belongs to. Kestrel Tech Camps is the atlas's fictional brand, and Priya is its sample parent.
- Iterable
- SMS
- Two-way texting
- Journeys
- Webhooks
- Compliance
A reply passes through three layers. The SMS provider handles the carrier-required keywords, STOP and HELP, before anything else. Answers to a question, like YES to a seat offer, are caught by the journey that asked it, which is waiting in a Hold Until tile. CALL and anything typed in plain words go to one Reply Router journey, started by the SMS Reply entry source.
Two places to catch a reply
Inside the journey that asked · Waitlist
The Reply Router · one journey for every family
Keywords live with the question. YES means "claim the seat" in the waitlist journey and "save us seats" in camp week. Only the journey that asked knows which, so each one catches its own answers and the router steps aside.
Every question closes. A Hold Until with no reply times out into a closing text that points to CALL. A YES that arrives after the hold has ended matches nothing, so the family has already been told what to send instead.
Same word, different jobs
| Reply | Asked by | Caught by | What happens |
|---|---|---|---|
| CAMPUS / ONLINE | Lead welcome & nurture | Hold Until | Saves preferredFormat and sends the matching guide |
| DATES | Browse abandonment | Hold Until | Texts the open weeks for the last course viewed |
| HOLD | Cart & checkout recovery | Hold Until | Holds the seat for 24 hours |
| YES / NO | Waitlist & seat alerts | Hold Until | Claims the held seat, or releases it |
| YES | Camp week | Hold Until | Saves seats at the Friday showcase |
| 0 to 10 | Post-camp survey | Hold Until | Records the score; 0 to 6 opens a support case |
| CALL | Any text | Reply Router | Creates a CRM callback task; an advisor calls within 2 business hours |
| HELP / STOP | Any text | SMS provider | Carrier-required help and opt-out |
| Anything else | Any text | Reply Router | Goes to an enrollment advisor's shared inbox |
Rules the router depends on
Replies are stored in lowercase. Iterable records smsMessage in lowercase, so every filter compares against "call", never "CALL", and capitalization never matters. Punctuation does: "Yes!" isn't "yes", so it goes to an advisor rather than being guessed at.
STOP and HELP come first. The SMS provider handles them before Iterable's journeys see anything, and the opt-out updates the number's subscription. No journey has to remember to honor them.
Only two-way senders take replies. The short code 53781 is two-way; an alphanumeric sender couldn't receive replies at all. MMS replies, like a photo, aren't supported.
Unknown numbers are ignored by default. A reply from a number with no profile still appears in the Inbound SMS log, but it doesn't create a person or start a journey unless placeholder profiles are switched on for the project.
Pick the last text the family got, then send a reply: tap a suggestion or type your own. The route and the event Iterable records update with each reply.
Route
Recorded in Iterable
When Priya replies CALL, the reply arrives as an event like the one below. The router's Webhook tile turns it into a callback task, filling the body with Handlebars from the event and her profile, including the lastQuiz answers from the quiz event page, so the advisor knows what she's shopping for before dialing.
{
"eventType": "smsReceived",
"email": "priya.k@example.com",
"fromPhoneNumber": "+12065550143",
"toPhoneNumber": "53781",
"smsMessage": "call",
"createdAt": "2026-06-02 15:04:11 +00:00"
}
{
"source": "sms_reply",
"email": "{{email}}",
"phone": "{{phoneNumber}}",
"parentName": "{{firstName}}",
"student": "{{lastQuiz.studentName}}",
"pathways": "{{join lastQuiz.pathways ", "}}",
"message": "{{smsMessage}}",
"receivedAt": "{{createdAt}}"
}
Once the CRM creates the task, it posts a callbackRequested event back to Iterable. The cart recovery journey uses that event as an exit rule, so Priya isn't texted about her cart while an advisor is already calling her.
That's the same exit-rule pattern the API page uses for purchases during an abandoned-cart wait.
{
"email": "priya.k@example.com",
"eventName": "callbackRequested",
"dataFields": {
"taskId": "CB-20417",
"channel": "sms",
"dueBy": "2026-06-02 17:04:11 +00:00"
}
}