← Back to all posts

Engineering

What actually gets a 10DLC campaign rejected

We registered our own brand and campaign through the API we built. It failed twice before it passed. Here is every reason, with the exact statuses, so you do not have to find out yourself.

Jose Zamudio · Founder, Handset

·6 min read

If you sell software to businesses in the US and you want your customers to text their customers from inside your product, you have to go through 10DLC. Every SaaS founder who adds texting meets it the same way: a carrier rejects something, the error says almost nothing, and the vendor tells you to read the FAQ.

I just went through it for Handset itself, using the compliance API we built. It failed twice before it passed. This is the write-up I wish had existed: what the pipeline really looks like, the exact statuses, the five things that blocked us, and what it costs.

What 10DLC is, in one paragraph

10DLC stands for 10-digit long code. It is the US carriers' system for sending application traffic (your app texting people) from normal local phone numbers. To send, a business registers a brand (who you are) and a campaign (what you send and how people consented) with The Campaign Registry (TCR). Carriers then decide how much traffic they will accept from that campaign. Unregistered traffic gets filtered, throttled, or blocked. The whole thing exists to kill spam, and it mostly works, which is why nobody gets to skip it.

The pipeline, with the real statuses

Nobody shows you this end to end, so here it is. We use Telnyx as the carrier under Handset; the status names are theirs, but every provider has the same stages with different labels.

Brand

  1. You submit legal name, EIN, address, phone, email, website.
  2. TCR matches your identity against IRS records. Your brand becomes VERIFIED. This is the gate for creating campaigns.
  3. Optionally you pay about $40 for external vetting, which gives you a trust score. This is a different thing from verification. It is not required. It only matters for throughput, which I will come back to.

The portal shows both next to each other: "Status: Verified" and "Vetting status: Unvetted". The second one looks like a problem. It is not.

Campaign

StatusMeaning
TCR_PENDINGSubmitted to the registry. The portal does not even list it yet.
TCR_ACCEPTEDTCR accepted the content and issued a campaign id (ours was CI0HTS7).
Telnyx reviewThe provider's own compliance team reads your opt-in flow. This is where the $15 review fee applies, every submission, including resubmissions.
MNO_PENDINGSent to the carriers (AT&T, T-Mobile, Verizon) for registration.
MNO_ACCEPTEDDone. You can attach numbers and send.
TCR_FAILED, TELNYX_FAILED, MNO_REJECTEDTerminal. Read the failure reasons, fix, resubmit.

Our campaign went TCR_PENDING to TCR_ACCEPTED in about an hour, through Telnyx review in a few hours, and was sitting in MNO_PENDING overnight. That is fast. The first attempt, on a different brand, took four submissions over a week and never made it.

Five things that blocked us

1. A required consent checkbox is a "forced opt-in." Our first opt-in form had a phone field and a consent checkbox, both required. The reviewer rejected it: if you cannot submit the form without consenting, it is not consent. The fix that passed: checkbox optional and labeled as optional, submission allowed without it, nothing stored and nothing sent unless it is checked. This is the single most common rejection I have seen people hit, and the error text does not explain it.

2. "Opt-in workflow" means every method, spelled out. The reviewer will browse your whole site looking for where a phone number could possibly be collected. If your campaign says "customers opt in on our website" and there is no form, or the form does not show the disclosure (message frequency, message and data rates, STOP and HELP instructions, a privacy policy link), it fails. We had to build the real form and describe it: the URL, what the checkbox says, the confirmation text we send after. If you also take numbers verbally or on paper, describe that script too.

3. Sample messages that never arrived. This one was on us. Our API took sample messages as an array and passed them to the carrier as an array. The carrier's API silently ignored that field and wanted sample1 through sample5 as separate fields. TCR then rejected the campaign with "Minimum sample messages not met for some MNOs." The campaign looked complete on our side. Lesson: after you submit, read the campaign back from the provider and check that every field you think you sent is actually there.

4. The STOP, HELP, and opt-in attestations. Same rejection listed subscriberOptin, subscriberOptout, and subscriberHelp: three booleans that say "yes, we handle opt-in, STOP, and HELP." They are required by some carriers, and the provider will not default them for you. Your platform also has to actually do what they claim: reply to HELP, stop on STOP, confirm on opt-in.

5. The $30 minimum balance. Campaign operations fail with a 402 unless the account holds at least $30. Not a review problem, just a wall you hit at the worst moment.

Smaller ones from the same day: keyword lists go in as comma-separated strings, not arrays, and the opt-in description must be at least 40 characters or the submission is refused outright.

What the carriers grant you afterward

Once the campaign is accepted you get a terms table per carrier. Ours, on a verified but unvetted brand with a customer-care use case:

CarrierManual reviewLimit
AT&TNo240 SMS per minute, 150 MMS per minute, message class E
T-MobileNoBrand tier LOW, 2,000 messages per day
VerizonNoManaged dynamically, no published number

Two things to notice. "Manual review: No" on every row means the last stage is automatic. And the only number that can ever bind you is T-Mobile's 2,000 messages a day per brand. That is what the $40 external vetting buys: a score that lifts the daily cap to 10,000, 200,000, or more. Do not pay for it until a real customer is anywhere near 2,000 a day on T-Mobile's share of phones.

Sole proprietor versus a real entity

If you do not have an EIN, you can register as a sole proprietor with your personal name and a cell phone OTP. We did this first. It is slower, reviewed harder, capped lower, and it puts your personal identity in a registry. Our sole-prop campaign failed review three times and then sat "pending" for a week. The standard brand under the company EIN verified the day we submitted it and its campaign was through TCR the same evening.

If you are going to do this for real, incorporate first. If your customers are the ones who need campaigns, they will each need their own EIN on their own brand, and you will be doing this once per customer.

Why we made campaigns an API object

That last sentence is the actual problem. One campaign is a bad afternoon. A hundred and twenty campaigns for a hundred and twenty customers, each with a hand-maintained row in a spreadsheet and a vendor ticket when it fails, is a job. We have seen that spreadsheet.

So in Handset, a brand and a campaign are objects you create with a POST, the status changes arrive as webhooks, and in test mode they approve instantly so you can build the flow before a real carrier is involved:

curl https://api.handset.dev/v1/campaigns \
  -H "Authorization: Bearer $HANDSET_API_KEY" \
  -d '{
    "tenant_id": "tnt_…",
    "brand_id": "brd_…",
    "use_case": "customer_care",
    "description": "Appointment updates and two-way support for plumbing customers.",
    "sample_messages": [
      "Ridge Plumbing: your technician arrives between 2 and 4pm today. Reply STOP to opt out.",
      "Ridge Plumbing: reminder, your water heater inspection is tomorrow at 9am."
    ],
    "opt_in_description": "Customers enter their number when booking and tick an optional consent box on the booking form; a confirmation text with STOP and HELP instructions is sent on enrollment."
  }'

You get back status: "pending_review" and later a campaign.status_changed webhook with approved or rejected and the carrier's reason. STOP and HELP handling, the confirmation message, and the attestations above are done by the platform. Every mistake in this post is now a validation error or a default.

If you are building texting into a vertical SaaS product and you would rather not learn all of this the hard way, the docs are at docs.handset.dev and there is a live demo at demo.handset.dev that needs no signup.

Share:XLinkedIn

More from Handset