A support ticket lands: "I never got my code." You check the logs. The message was sent. Status: delivered to the carrier, then nothing. The user is now staring at a login screen that won't let them in, and there's no retry button that fixes a carrier-level filter.

This is one of the quietest, most expensive failure modes in SaaS SMS, and it almost always traces back to the same root cause: your one-time passcodes and your promo blasts are riding the same 10DLC number, registered under the same campaign. When a carrier decides your traffic looks like marketing, it applies marketing-grade scrutiny to everything on that number — including the six-digit code your user needs to get into their account.

Full disclosure: I work for Ready, an SMS platform. We handle 10DLC registration and campaign use-case setup in-app, so I've watched this exact mistake play out across a lot of accounts. Here's why it happens and how to structure around it.

Why one number can't be both "critical" and "promotional"

10DLC (the A2P messaging framework for standard 10-digit US long codes) doesn't just register your number — it registers a campaign describing what you send. Every campaign carries a use case: transactional, marketing, 2FA/OTP, account notifications, and so on. Carriers use that declared use case to set expectations for the traffic, and they filter against those expectations in real time.

The problem with one shared number:

  • OTP/2FA traffic is time-sensitive and non-optional. The user asked for it two seconds ago. Late delivery is the same as failed delivery.
  • Marketing traffic invites complaints. Even a great list generates STOPs and spam reports. Carriers watch complaint rates per number.
  • A shared number pools the risk. When your promo blast racks up complaints, the carrier's trust score for that number drops — and your OTP messages ride the same number. The filter that was meant to slow your marketing throttles your login codes too.

You didn't do anything wrong to the OTP campaign. It got caught in the blast radius of the marketing campaign it shares a number with. We wrote a companion piece on this exact split from the deliverability angle: sending 2FA codes and promo blasts from the same number gets both filtered.

What carriers actually see when the codes are late

Delivery status in your dashboard can lie by omission. "Sent" often means "handed to the carrier," not "landed on the phone." When a carrier filters a message post-handoff, you frequently get no explicit failure — it's a silent drop.

That's the trap. Your metrics look fine. The 99.x% delivery rate on your dashboard is measuring the wrong thing. Meanwhile, T-Mobile in particular applies aggressive per-number filtering, and it doesn't apply it evenly — the same campaign can fly on AT&T and crawl on T-Mobile. We covered that asymmetry in same campaign, different carriers. If your OTP failures cluster on one carrier, that's a filtering fingerprint, not a coincidence.

The fix: two campaigns, two numbers, matched use cases

The clean structure separates traffic at the registration layer:

OTP / MFA numberMarketing number
Use case2FA / OTP (or account notification)Marketing / promotional
Content"Your code is 448210" — no links, no branding fluffOffers, links, campaigns
Opt-inImplicit at account creation / loginExplicit marketing consent
Complaint exposureNear zeroNormal for marketing
What a filter doesIsolated — can't be dragged down by promo complaintsContained to marketing only

The point isn't just "buy a second number." It's that each number's declared use case now matches what actually flows through it. Carriers reward that match with cleaner delivery, and they punish the mismatch — registering a "marketing" campaign for what's really transactional traffic is itself a documented cause of silent delivery drops (here's why).

Get the use case right at registration

Two mistakes we see constantly:

  1. Registering everything as "marketing" because it's the broadest bucket. It's also the most heavily filtered. Your OTP traffic inherits that scrutiny.
  2. Registering OTP under a low-throughput use case, then wondering why codes queue during a signup spike. Match the use case to real volume patterns.

In Ready, campaign registration is handled in-app — brand and campaign setup, use-case selection, and sample messages, with most approvals landing in 1–3 days (often same-day). That matters here because splitting traffic means running two campaigns, and you don't want the second registration to become a multi-week project. If you're new to the framework, our 10DLC explainer walks the whole thing.

Write OTP samples that actually pass

Even with the right use case, your sample messages have to survive review. OTP campaigns get rejected for surprisingly dumb reasons:

  • No opt-out language expectation clarity. OTP is generally exempt from STOP requirements because it's user-initiated, but your sample and campaign description need to make that context obvious.
  • Embedded links in the code message. A login code with a shortened link looks like phishing to a reviewer and to a filter. Keep OTP messages link-free.
  • Vague sender identity. "Your code is 448210" beats "Here's your verification" with no brand context — but don't stuff it with marketing copy either.

If your campaign keeps bouncing on the sample, this rewrite guide covers what reviewers flag and how to phrase around it.

Approved isn't the finish line

Here's the part teams skip: getting a campaign approved doesn't make it immune to content-level filtering. Approval clears you at the registration layer. Carriers still inspect message content in real time, and a message that trips a content filter gets dropped even on a perfectly registered number. Public URL shorteners are a classic trigger — a shared bit.ly link in your traffic can silently sink delivery (the dedicated-domain fix is here). More broadly, approved-then-still-filtered is its own documented failure mode.

For OTP this is easy to get right precisely because the message is boring: a code, a brand name, nothing else. Keep it that way. The moment someone adds "and check out our new feature!" to the verification text, you've turned a clean transactional message into borderline marketing content — on a number whose reputation your entire login flow depends on.

A quick cost-and-risk sanity check

The extra number and the second campaign registration cost roughly $10/mo per brand and $20/mo per campaign in carrier fees. Set against the alternative — support tickets, churned users who couldn't log in, and a filtered OTP flow you can't debug from the dashboard — that's not a close call.

Worked example: say 2% of your logins fail their first OTP because of shared-number filtering, and you do 5,000 logins a day. That's 100 users a day hitting a wall, some fraction of whom open a ticket or abandon. Even at a conservative $8 fully-loaded cost per support contact, a fraction of that 100 dwarfs the ~$30/mo it costs to isolate the traffic. The math points one direction.

If you want to price out the segment-level side of your traffic, Ready's SMS is $0.02/segment on Standard (0–50,000/mo), dropping automatically to $0.016 past 50,000 in a calendar month, plus the transparent $0.0045/segment carrier pass-through. Details on the product page.

The practical takeaway

  • Your OTP/MFA traffic and your marketing traffic should never share a 10DLC number.
  • The reason is structural: carriers score reputation per number, and marketing complaints drag down everything on that number — including login codes.
  • Fix it at registration: two numbers, two campaigns, use cases matched to what each actually sends.
  • Keep OTP messages link-free and boring. That's the point.
  • Approval isn't immunity — content filtering still applies, so watch shorteners and marketing copy creep.

If you're running both critical and promotional SMS through one number today, the first step is just checking what use case your current campaign is registered under. If it's "marketing" and you're also sending codes, that's your leak. You can spin up the second campaign in-app and start with 2,500 free credits, no card required — enough to test the split before you commit a dollar.