The Text Message Became a Registered Product

In the United States, sending a text from an application now means filing a legal name, a tax identifier and an address with a central registry the carriers read, and then registering each kind of message you intend to send. This is about A2P 10DLC: what got added between the API call and the phone, why daily throughput arrives as an allocation rather than capacity, and why a filtered message is acknowledged rather than refused. Three green dashboards can describe a message nobody received.

The Integration That Took an Afternoon

The integration takes an afternoon. You POST a destination number, a source number and a body to an endpoint, you get back an accepted status and an identifier, and somewhere under a second later a phone buzzes. It is the most satisfying dependency in the catalog, because there is nothing to it. No schema to negotiate, no partner to onboard, no format anyone argues about. It has looked exactly like this for a decade.

That stability is the tell, and I missed it for longer than I would like to admit. The API did not change. Everything the API is a facade over changed, and it changed in the direction that APIs are specifically designed to hide from you. Somewhere between 2020 and 2022 the American carriers converted application-originated messaging from a thing you send into a thing you are permitted to send, and the permission is granted per business, per use case, in advance, by a third party.

The mechanism is called A2P 10DLC, which is one of those names that manages to be an acronym twice. It covers application-to-person traffic from ordinary ten-digit long codes, which is to say from the sort of local number a small business would use, as opposed to a short code or a toll-free number. If you send from a normal phone number and a program wrote the message, this is you. Nearly everybody is you.

The interesting part is not the telecom. It is what happens to a system when the thing that decides whether its output arrives moves outside its boundary, keeps the same interface, and stops reporting failure. That describes a category of dependency, not a channel, and I would rather have learned the shape of it somewhere cheaper than a password reset that never landed.

What Got Added Underneath

A brand is a legal identity, not an account. Before any traffic flows you register the sending business: legal name, tax identifier, address, website, a person who answers for it. That record goes to The Campaign Registry, a central registry the carriers read, and it is vetted against third-party business data rather than taken on faith. This is not an account creation form with a nicer label on it. It is the sort of identity check a bank performs, sitting in front of an HTTP endpoint.

A campaign is a declared use case. On top of the brand you register each kind of message you intend to send, with sample text, the opt-in language, where the consumer encountered the call to action, and how they opt out. Two-factor codes are one campaign, delivery notifications are another, marketing is a third and is treated with more suspicion than either. The declaration is reviewed. What you may send is bounded by what you said you would send, months earlier, in a form.

The price gained a component you do not control. Per the aggregators' documentation, AT&T stood its program up in March 2021 and ended the fee grace period on 1 May 2021, from which point a carrier network access charge applied per message, on top of whatever your provider bills. There are one-time registration charges and a recurring monthly charge per campaign as well. The absolute amounts are small. The structure is not: a line item in your unit economics is now set by a party you have no contract with.

Throughput arrives as an allocation. The last piece is the one that surprises people. Registered brands are placed in tiers, and the tier sets how many message segments per day the carrier will accept, with the published tables reserving the top allocation for companies large enough to sit in a stock index. You do not provision this and you cannot buy your way up it on a Tuesday. It is assigned, from a score, computed by someone else, from evidence about your business.

Allocation Is Not Capacity

Capacity you can plan. If a queue drains at a known rate you can reason about backlog, buy more of it, or shed load deliberately when the rate is not enough. Every instinct an engineer has about a rate limit assumes the limit is a number attached to a resource, and that resources can be bought. That instinct is correct almost everywhere, and it is wrong here.

Eligibility you can only apply for. Raising a daily allocation means resubmitting a business identity for review, or registering a new campaign and waiting for it to be approved, and the wait is however long a human review queue is that week. So the lead time on additional messaging capacity is not an engineering lead time at all. It is somebody else's compliance backlog, and it does not respond to urgency, incident severity, or the fact that you are willing to pay more.

It is also a judgment that reaches traffic you already send. Reclassification is retroactive in effect: the messages you have been sending successfully for a year get evaluated under this quarter's rules, not the ones in force when you registered. Nothing deployed, nothing in your code changed, and the behavior of a production dependency moved anyway. There is no rollback for a decision taken in somebody else's database, and no release note announcing it either.

I want to be careful not to write this as a grievance, because the regime is not arbitrary. The FCC's 2018 ruling notes 1.77 trillion messages exchanged in the United States during 2017, on a channel whose value rests almost entirely on not being full of garbage. Something had to hold that line, and checking the sender's identity is a more honest instrument than reading everyone's content. The cost is real too, and the smallest senders pay it.

Filtered, Not Rejected

Filtering is not rejection. A message a carrier declines to deliver is very often accepted, acknowledged and dropped, and the sender is not told which. This was on the record long before 10DLC: in the docket behind the FCC's December 2018 declaratory ruling, one commenter described there being no feedback loop saying messages were blocked or why, and another wrote that legitimate messages were blocked without notice or explanation. The ruling classified SMS and MMS as information services rather than common carriage, so there is no delivery obligation to appeal to.

Every layer above the carrier is therefore reporting on the wrong thing. The API returned a 202 because the message was accepted for sending, which it was. The provider marks it sent, then delivered, on the strength of whatever the downstream leg reported, which may be a handoff rather than a handset. Three green indicators, in three systems, all of them honest, describing a message nobody received.

That is the general lesson and it is not about telephones. A dependency whose failure is invisible from inside your system cannot be monitored from inside your system, and no amount of instrumentation fixes it, because instrumentation measures what your code can see. The check has to leave the building. CTIA's messaging best practices, published in July 2019, gives a chapter to containing unwanted traffic, with sections on blocking senders and disconnecting them. Read it as the industry describing how your messages stop without anyone telling you.

Checks That Run Outside

Use a real number that a person reads. Put a physical handset, or a number a colleague actually watches, into a scheduled job that sends one message an hour through the production path with a timestamp in it. Alert when the gap between what was sent and what arrived exceeds a threshold. It is unglamorous, it costs almost nothing, and it is the only signal in the entire stack that is generated on the far side of the boundary.

Keep a delivery baseline per carrier, not overall. Aggregate delivery rate hides the failure, because filtering is applied by one carrier at a time and one carrier is a minority of your traffic. Split the rate by destination network and keep a baseline for each. A drop from ninety-something percent to fifty on a single carrier is unmistakable in that view and completely invisible one row up, which is the row every dashboard shows by default.

Never let a text be the only path to a login. If a code sent by SMS is the sole route back into an account, then a filtering decision at a carrier is a total outage of your authentication for whoever it touches, and it will present to support as isolated user complaints rather than as an incident. A second factor type, or an email fallback, converts a silent dependency failure into a degraded but working path. This is cheap to build first and unpleasant to retrofit under pressure.

I should narrow this before it becomes a complaint about carriers, because I think they were roughly right. The channel works because it is trusted, it is trusted because it is not full of fraud, and the sender's identity is a sturdier thing to check than the message's content. If the price of a text people still open is that a registry knows who sent it, that is a decent trade, and the version of this argument that demands the old free-for-all wants a channel nobody would read.

What stays with me is smaller. There is something in your architecture diagram drawn as a single arrow, added in an afternoon, that now depends on a decision in a database you cannot query, by a party you have no contract with, under rules that changed since you integrated. It has not failed yet, as far as you know. That last clause is the entire problem, and the only fix runs outside the system: a real handset, one message an hour, in front of somebody who will notice when the buzzing stops.