Your users are asking for texting. Maybe it is "on my way" messages to the customer, appointment reminders, or just letting a contractor reply to a homeowner without handing out a personal cell number. It sounds like a weekend feature. Add an SMS provider, wire a send button, ship it.
It is not a feature. It is a small telecom company you are about to accidentally start, inside the SaaS you actually meant to build.
The demo that lies to you
Every SMS provider has the same five-minute demo, and it is true:
await client.messages.create({
to: "+14155551234",
from: "+14155550000",
body: "Your appointment is confirmed.",
});One call, one text, done. The demo works because it shows you the tip of the iceberg on a sunny day. Everything expensive is below the waterline, and you do not find it until you are shipping to real customers.
The iceberg
Here is what "just add texting" actually signs you up for, in the order it tends to hit you.
10DLC registration, and not just yours. Sending application-to-person texts from a US long-code number is not open by default anymore. Every business that sends needs a registered brand and a registered campaign in The Campaign Registry (TCR), with a use-case, sample messages, and opt-in language. Carriers reject campaigns for reasons that read like tea leaves. And here is the part nobody tells the vertical-SaaS founder: if each of your customers texts from their own number, each of your customers needs their own registration. You are not registering once. You are running a registration pipeline, per customer, forever.
A number per customer. Multi-tenant texting means tenant A and tenant B send from different numbers, in their own area codes, that they recognize on the invoice. So now you are procuring numbers by area code, attaching them to the right tenant, releasing them when a customer churns, and keeping the mapping straight.
STOP, HELP, and opt-out, which are the law. When someone texts STOP, you must stop, immediately and permanently, per number. HELP must respond. Opt-out lists have to be honored even when a different part of your app tries to send. This is not a nicety. It is TCPA, and the fines are per message.
Two-way, which is a whole inbox. The moment a customer can reply, you need inbound webhooks, message threading, conversation state, and a way to show tenant A only tenant A's threads. Delivery receipts, error codes, and carrier surcharges show up on the same day.
Multi-tenant isolation, which is the scary one. In a single-tenant app, a leak is embarrassing. In a multi-tenant messaging system, tenant A seeing tenant B's customer conversations is a breach. Every query, every webhook, every number lookup has to be scoped, and it has to be scoped correctly the first time.
None of that is your product
Read that list again and notice what is missing from it: your actual product. You set out to build scheduling, or invoicing, or a CRM for a specific trade. What you would be building instead is number procurement, a compliance pipeline, an opt-out ledger, and a multi-tenant message store. That is telecom infrastructure. It is a real business, and it is not the one you are in.
The trap is that each piece looks small on the day you hit it. The number-per-tenant thing is an afternoon. STOP handling is an afternoon. Then 10DLC rejections eat a week, a carrier changes a rule, a customer's campaign gets flagged, and you realize you now have an on-call rotation for text messages.
What the shortcut actually looks like
The way out is not a cleverer SMS library. It is an API where the multi-tenant, compliant shape is the thing itself, not something you assemble on top.
Concretely, that means: a tenant is a first-class object that owns its own numbers, conversations, and opt-out list, so isolation is the default instead of your responsibility. Opt-out and STOP handling are automatic on every number. 10DLC brand and campaign registration happen through an API call in your onboarding flow instead of a spreadsheet and a vendor ticket. And two-way messages arrive already threaded by conversation, so an inbox is a read, not a build.
The honest part: nobody lets you skip 10DLC. It is the law of the land on US carriers, and any provider that implies otherwise is setting you up for a suspension. What you can skip is operating it by hand. The difference between POST /campaigns in your signup flow and a human maintaining 126 rows in a Google Sheet is the difference between a feature and a department.
Build against a fake carrier first
The other thing that makes this bearable: you should not have to register anything, buy a number, or risk texting a real person just to build the feature. On Handset, test-mode keys run against a simulated carrier. Numbers are free and instant, sends and replies are simulated end to end, and compliance is stubbed, so you can build and demo the entire texting experience before a single real registration exists. You flip to live only when the product is done and the customer is real.
That is the whole pitch, and it is deliberately unglamorous: texting inside your SaaS is not one API call, it is a dozen obligations, and the point of a platform is to hand you the obligations already met so you can go back to building the thing your customers actually pay for.
If you are staring at an "add texting" ticket and starting to see the iceberg under it, that is the correct reaction. Handset is the business phone and texting API built for exactly this shape: real numbers, two-way SMS with opt-out and 10DLC handled, multi-tenant by default, one REST API with TypeScript, Python, and Go SDKs. Build it against the simulated carrier this afternoon, and never start the telecom company you did not mean to.
More from Handset