Two-way SMS: text your families back from inside Presently
September 5, 2026 · 8 min read · by James, half-owner of a dance studio
For a while now, Presently has been able to send outbound SMS. A studio owner types a message, picks a family or a whole class, clicks Send, and Twilio fires it off. It worked. It just wasn't the whole picture.
The whole picture is: a parent replies. And then what.
Until this week, the answer at most studios was "the reply goes into the void." Twilio received it, we didn't route it anywhere, and the parent's follow-up ("is class still on tonight?") landed in nobody's inbox. The studio owner either had a browser tab open to the Twilio console and could see the reply there, or the reply was simply lost.
So bi-directional SMS shipped. When a family texts your Twilio number, the message now lands in an SMS Inbox in the admin site with a real-time badge, threaded into that family's Communications tab alongside every email you've ever sent them. You can reply directly from either surface, and the reply goes back over SMS from the same Twilio number the family already knows. The whole thing runs on your own Twilio account so the studio owns the phone number and the messages.
How it works from the studio's side
A parent texts your Twilio number. Twilio POSTs the inbound message to a webhook URL we expose. The webhook figures out which tenant owns that Twilio number, verifies the request actually came from Twilio (signed against your auth token), and writes the message.
The message shows up in the SMS Inbox in real time. There's a new sidebar item under Communications called "SMS Inbox." It has three filter tabs — All, Unread, Unmatched — and an unread-count badge that ticks up the second a new inbound arrives. No polling, no page refresh; the badge just moves. If you're on the Inbox page when a new message lands, the row slots into the list immediately.
The same message threads into the family's Communications tab. Open a family, click Communications, and you see a single interleaved timeline: outbound emails, outbound SMS, inbound SMS, all sorted newest first with distinct icons for each. Every touchpoint the studio has ever had with that family in one place, from either direction.
You reply from wherever's convenient. There's a Reply button on every inbound row in the Inbox, a Send SMS button on the family Communications tab, and the existing Send SMS modal everywhere it already lived. Same underlying job in every case — one Twilio account, one phone number, one audit trail.
Unknown senders
One thing that comes up more often than you'd think: a parent texts your studio from a phone that isn't on their account. Grandma dropping off the kid uses her own cell, or Mom borrows Dad's phone, or the family just never got around to entering their number in the portal.
Every SMS Inbox has an Unmatched filter tab for exactly this. Inbound messages from numbers we don't recognise land there with an amber "Unknown sender" chip. Next to each one is a "Match to Family" button — click it, search for the family, and we attach the number to their account. As a bonus, we back-fill every previous unmatched message from the same phone so if Grandma has been texting for three months from an unknown number, they all get linked to the right family in one action.
STOP, HELP, and the compliance piece
This is the part most SMS features hand-wave through, and it's the part that matters. US SMS is regulated. The CTIA guidelines say a family that texts STOP has to be opted out immediately, and the studio has to know that happened.
Presently handles STOP two ways at once. Twilio's Messaging Service blocks future sends at the carrier level the moment they see STOP — that's automatic and it happens even if this app never runs a line of code. But we ALSO watch for STOP on the way in and flip both consent flags on that specific phone number to false, timestamped and audited. This means the admin UI stops showing that phone as eligible to send to; every future compose screen filters it out; and if the same phone shows up in a different Twilio account later (rare, but possible), the block travels with our record instead of just Twilio's.
Same shape for START and HELP. START re-enables operational SMS (the safer default — parents opting back in via keyword shouldn't automatically re-enable promotional too; that requires explicit re-consent). HELP just gets logged so you can see it in the timeline.
Every inbound row that came in as a STOP shows a callout: "Recognised as STOP — opt-in flags cleared automatically." So there's no mystery about why a family that used to receive texts suddenly isn't.
Operational vs. promotional
A while back we split family SMS consent into two categories: operational (rain delays, class cancellations, autopay reminders) and promotional (upcoming summer camp, spring recital signups, new class announcements). A family can opt into one, both, or neither — per phone number, not per family.
Every compose screen — the full SMS compose page, the per-family Send SMS modal, the per-class Send SMS modal — has an Operational/Promotional selector. The recipient count filters live: flip to Promotional and any family who only opted into Operational drops out of the list before you hit Send. What you see is what actually gets delivered.
This isn't over-engineering for the sake of it. If a family gets a promotional text they explicitly opted OUT of, that's a carrier violation and a possible complaint to CTIA. The category split makes the compliance boundary visible in the UI so a well-meaning studio can't accidentally cross it.
Admin notifications
Optional but useful: when an inbound SMS arrives, every admin who's opted in can get an email. Same shape as the "new enrollment" or "new family" emails you might already have on — tenant-level toggle under Settings, per-admin toggle under Profile. Default off so an existing tenant doesn't suddenly start getting a flood of "new inbound SMS" emails on day one.
The email includes the sender, the message body, the recognised-as-STOP callout if applicable, and a link that deep-links straight into the family's Communications thread. One click from the notification into the reply.
How the tenant gets set up
The whole inbound flow depends on Twilio knowing to POST inbound messages to Presently. So there's exactly one setup step:
Settings → SMS now has an Inbound SMS webhook panel with a copyable URL. Paste that URL into the Twilio phone number's "A message comes in" field (HTTP POST). Done. The URL is the same for every tenant — routing to the right studio happens on our side keyed on the "To" phone number in the payload, so there's no per-tenant subdomain to remember.
Because we validate every inbound webhook against your Twilio auth token, the URL is safe to expose publicly. A random attacker POSTing garbage to that endpoint gets rejected with a 403 before we touch the database.
What we're deliberately NOT doing
A few decisions worth naming:
No MMS attachment ingestion into our own storage. When a family sends an SMS with an image, we capture the Twilio-hosted URL and render it in the thread as a link. We don't download and re-host to R2. Reason: Twilio media URLs are stable for weeks, and rehosting adds storage cost + a code path that has to keep working across Twilio's API changes. If a studio actually needs the photo long-term they can download it manually from the link. If enough studios start asking for permanent archiving, we'll build it.
No inbound-SMS-triggered automation. A family texting "I need to make up my class next week" doesn't kick off a makeup-request workflow. It just lands in the Inbox for a human to handle. Automation on top of inbound is where every SMS system I've ever seen goes off the rails — a parent's ambiguous "yes" gets interpreted as consent to a $50 charge, and now you're refunding it and apologising. Presently's answer is "an admin reads it and acts on it."
No cross-family threading. Every SMS Inbox thread is family-scoped. If Grandma and Mom both text from different phones about the same kid, those live in the same thread once you've matched Grandma's phone. But two unrelated families can't accidentally see each other's messages.
No AI auto-replies. Not even offering it as an opt-in. The stakes of an AI-drafted reply to a parent who just asked "will there be class tonight, my daughter is sick" are asymmetric — right answer saves you a phone call, wrong answer means a parent shows up at a locked door in the rain.
What this actually feels like
I've been running this on my own studio's account for two weeks now. The unremarkable version, which is the good version: parents text the studio number, I see it, I reply, they get the reply, done. The Inbox surfaces new messages without a page refresh so I don't have to remember to check. The Communications timeline on a family is now a single scrollable record of every conversation, which was the actual thing I always wanted.
A couple of specific things I didn't expect to matter but do:
The unread-badge-in-the-sidebar pattern is quietly load-bearing. I don't have to be on the Inbox page to notice; the sidebar tells me. It behaves the way an unread email count does on your phone, and it means I don't miss things.
Interleaving inbound SMS into the family Communications tab makes an oddly outsized difference to how I think about a family. Before, I'd open a family and see a list of the automated emails we'd sent them. Now I see the actual dialog, and the family reads as a relationship instead of a billing record.
Also, STOP handling working end-to-end automatically is worth the whole project. There's no scenario now where a family says "please stop texting me" and we accidentally text them anyway a week later because the studio owner forgot to update a checkbox. The system took care of it.
What's still worth adding
Deliverability visibility. When Twilio can't deliver a message (bad number, carrier block, spam filter), we don't currently surface that in the compose UI. The message shows "sent" from Presently's side because our SendSms job completed. Twilio's delivery-status webhook is a separate integration that we haven't wired yet. Coming.
Per-tenant Twilio number ownership is already there — but the setup instructions right now assume you already have a Twilio account and know how to pick a number. The setup wizard should probably walk a first-time studio through Twilio signup → number purchase → A2P registration in the same flow. Todo.
Bulk match for unmatched senders. If Grandma texts once, it's one match. But some studios end up with a Un-matched bucket of 20+ messages from unknown numbers over months. Right now each has to be matched one at a time. A "these all seem to be the same phone, match all" chunk action would help.
For now: the loop is closed. Send an SMS from Presently, get a reply back into Presently, and the whole conversation lives in one place — with consent, opt-out, and compliance handled by the software instead of by hoping the studio owner remembers.
Turn it on and text yourself.