DEV Community

Cover image for The Hidden Cost of Phone-Based Auth: What I Learned After 18 Months
Mike Davis
Mike Davis

Posted on

The Hidden Cost of Phone-Based Auth: What I Learned After 18 Months

We added SMS verification to our app expecting it to be simple. 18 months later, I have opinions about carrier filtering, Twilio bills, and abuse bots.


Eighteen months ago, our PM walked into standup and said: "We need phone verification for sign-up. Users are creating fake accounts with disposable emails. Should be a weekend project, right?"

It was not a weekend project.

What followed was a year and a half of unexpected Twilio invoices, ghost messages eaten by carrier filters, an abuse wave that nearly bankrupted our SMS budget, and a slow realization that phone-based auth is one of those things that looks simple on a whiteboard and gets complicated the moment real humans in real countries try to use it.

This isn't a tutorial on how to implement 2FA. There are plenty of those. This is the stuff nobody warned me about.

The Twilio Bill That Made Finance Call Me

Our app is a B2B SaaS tool — think project management for small agencies. We launched phone verification for sign-up in March 2024. The plan was straightforward: user enters phone number, we send a 6-digit code via Twilio, user enters code, done.

I estimated the cost based on our existing user growth: ~800 new sign-ups per month, $0.0079 per SMS to US numbers. That's about $6.30/month. I told finance to expect $10–15/month with some buffer.

The first month's bill was $47.

Here's what I didn't account for:

Retries. Users don't always receive the first SMS. They click "resend." On average, each successful verification took 1.4 messages — some users hit resend 3–4 times. That's a 40% overhead on every estimate you'll ever make.

International numbers. We're US-based, but about 30% of our users aren't. Sending an SMS to India costs $0.0262. Germany is $0.0637. UAE is $0.0587. That "30% international" slice was eating 70% of the budget.

Failed deliveries you still pay for. Twilio charges you when the message is accepted by the carrier, not when the user receives it. If the carrier silently drops it — and they do, more often than you'd think — you still pay.

By month three, we were spending $120/month. By month eight, after a Product Hunt launch bumped sign-ups, we hit $340 in a single month. Not catastrophic, but a far cry from the "$10–15" I promised.

Carrier Filtering: The Silent Killer

The cost was annoying. The delivery failures were worse.

Around month four, our verification success rate dropped from ~92% to ~71% over two weeks. No code changes. No infrastructure issues. Support tickets flooded in: "I never received the code."

It took me three days to figure out what happened. The answer: carrier filtering.

Mobile carriers run spam filters on application-to-person (A2P) SMS traffic. If your messages look like they're coming from a bot — same content, high volume, short codes — carriers can silently drop them. No error code. No bounce notification. The message just vanishes.

What triggered it for us was a combination of factors:

Message template. Our OTP message was: Your verification code is: 482901. That's basically the platonic ideal of an A2P message. Every spam filter in the world is trained to recognize this pattern.

Volume spike. The Product Hunt traffic tripled our daily SMS volume overnight. Sudden spikes are a classic spam signal.

Shared short code. We were using Twilio's shared short code pool. Our messages were being sent from the same numbers that other Twilio customers used — some of whom were probably sending actual spam.

The fix involved three changes:

First, I switched to a dedicated Twilio Messaging Service with a registered A2P 10DLC campaign. This is Twilio's system for whitelisting your traffic with US carriers. The registration process took two weeks and cost $15 — but delivery rates jumped back to 91%.

Second, I added slight variations to the message body. Instead of the same template every time, I rotate between a few versions and include the app name. Small thing, but it helps avoid pattern-based filtering.

Third — and this was the expensive one — I added Twilio Verify as a fallback. When our primary SMS channel fails, we fall back to Twilio's managed verification service, which handles carrier negotiation on their end. It costs more per message ($0.05 vs $0.0079), but the delivery rate is significantly higher.

The Abuse Wave

Month eleven. A Wednesday morning. I'm looking at our dashboard and notice something odd: 2,400 new accounts created in the last 6 hours. Our normal daily sign-up rate is about 40.

Someone was bot-registering accounts using virtual phone numbers.

The irony is not lost on me — I now use virtual numbers myself for testing (I wrote about that in my previous post about mocking 2FA). But being on the receiving end of it was educational.

Here's what the attack looked like:

  • Automated script hits our registration API
  • Provides a phone number from a virtual number pool (we later identified the numbers as VoIP-based, mostly from Southeast Asian providers)
  • Receives OTP via the virtual number service
  • Completes registration
  • Immediately uses the account to post spam in our public project boards

The attacker was spending maybe $0.02 per virtual number. We were spending $0.05–0.10 per verification attempt (including retries and Twilio Verify fallback). They were literally making us pay to get spammed.

We burned through $180 in Twilio credits in a single morning before I shut it down.

What We Did About It

Short-term: I added rate limiting by IP and by phone number prefix. More than 5 registrations from the same /24 IP block in an hour → CAPTCHA. More than 3 attempts with the same country code + first 4 digits → block for 30 minutes.

Medium-term: I integrated a phone number intelligence API (Twilio Lookup) to check whether a number is VoIP, landline, or mobile before sending the OTP. VoIP numbers get an extra verification step (email confirmation + CAPTCHA). This costs $0.005 per lookup but saved us far more in prevented abuse.

def check_number_type(phone: str) -> str:
    """Check if a phone number is mobile, landline, or VoIP."""
    resp = twilio_client.lookups.v2.phone_numbers(phone).fetch(
        fields="line_type_intelligence"
    )
    line_type = resp.line_type_intelligence.get("type", "unknown")
    return line_type  # "mobile", "landline", "voip", "unknown"

def should_require_extra_verification(phone: str) -> bool:
    number_type = check_number_type(phone)
    return number_type in ("voip", "unknown")
Enter fullscreen mode Exit fullscreen mode

Long-term: We added passkey support as the primary auth method and made phone verification optional for users who set up a passkey. More on this below.

The Country Problem

I mentioned international SMS costs earlier, but there's a deeper issue: SMS doesn't work the same way everywhere.

India requires pre-registered DLT templates. If you're sending OTP messages to Indian numbers, you need to register your message template with the Telecom Regulatory Authority of India through a DLT portal. Twilio can handle this, but the registration takes 1–3 weeks and requires a local business entity or partner. We didn't know this until Indian users started reporting zero delivery.

China is effectively off-limits for most Western SMS providers. Carrier restrictions, content filtering, and regulatory requirements make it nearly impossible to reliably deliver A2P SMS from a US-based Twilio account. We ended up not supporting Chinese phone numbers for verification and offering email + TOTP as an alternative.

Brazil has specific regulations around SMS consent and a DNC (Do Not Call) registry that applies to certain types of SMS. We had to add explicit opt-in language to our verification flow for Brazilian numbers.

Several African countries have delivery rates below 60% through Twilio. We tested Vonage as a secondary provider for specific routes and saw marginally better results for Nigerian and Kenyan numbers, but it's still not great.

The lesson: if your user base is international, budget 2–3x what you'd estimate for US-only, and expect to spend significant engineering time on country-specific edge cases.

The Auth Method Comparison I Wish I'd Done Earlier

After 18 months of dealing with all of the above, I sat down and compared the actual cost and reliability of every auth method we could reasonably offer. Here's the real-world data from our app:

SMS OTP

  • Setup effort: Medium (Twilio integration, A2P registration, carrier troubleshooting)
  • Per-verification cost: $0.02–0.08 (varies wildly by country)
  • Delivery success: ~91% US, ~75–85% international
  • Abuse resistance: Low without additional checks (Lookup API, CAPTCHA)
  • User friction: Medium (wait for SMS, type code)

Email magic links

  • Setup effort: Low (you already have an email provider)
  • Per-verification cost: ~$0.001 (practically free with any transactional email service)
  • Delivery success: ~97% (spam folders are the main failure mode)
  • Abuse resistance: Medium (disposable email services exist, but they're easier to block)
  • User friction: Medium-High (switch to email app, find message, click link, switch back)

TOTP (authenticator app)

  • Setup effort: Low (pyotp + QR code generation)
  • Per-verification cost: $0.00 (runs locally on user's device)
  • Delivery success: 100% (no network dependency)
  • Abuse resistance: High (requires a real device with an authenticator app)
  • User friction: High for setup, Low for subsequent use

Passkeys / WebAuthn

  • Setup effort: High (browser API, credential storage, fallback flows)
  • Per-verification cost: $0.00
  • Delivery success: 100% (local biometric/PIN)
  • Abuse resistance: Very High (tied to physical device)
  • User friction: Very Low once set up (fingerprint/face)

Looking at this table, SMS is the worst option on almost every metric except one: user familiarity. Everyone knows how to receive a text message. Not everyone knows what an authenticator app is. Passkeys are amazing technically, but try explaining them to a non-technical user who just wants to sign up for a project management tool.

That familiarity is worth a lot. It's the reason SMS verification isn't going anywhere despite being expensive, unreliable, and abuse-prone. People understand it.

Where We Landed

Today, our auth flow looks like this:

  1. Primary: Email magic link for registration (cheap, reliable, good enough for initial sign-up)
  2. Optional upgrade: Passkey enrollment after first login (we nudge users with a dismissible banner)
  3. Phone verification: Required only for specific actions (inviting team members, accessing billing, exporting data) — not for basic sign-up
  4. TOTP: Available as an alternative to phone verification for users who prefer it

This reduced our Twilio bill from $340/month at peak to about $80/month, because we're only sending SMS for high-value actions, not every single registration. The abuse problem essentially disappeared because bots can't use email magic links to get to the phone-gated features without maintaining a persistent session.

Things I'd Do Differently

Start with email-first auth, add phone later. We went straight to phone verification because it felt more "secure." It is, marginally — but the operational cost is 10–50x higher than email, and the security improvement is negligible for most apps that aren't handling financial transactions.

Budget for international SMS from day one. If you have any international users, multiply your US-only SMS cost estimate by 3x. If you have users in India, China, or parts of Africa, multiply by 5x and add a line item for "country-specific compliance headaches."

Implement phone type checking before sending. The $0.005 Twilio Lookup call pays for itself many times over by filtering VoIP numbers, catching typos (landline numbers), and preventing abuse.

Run your verification flow through real E2E tests. I wrote about this in my previous post — mocking your OTP layer hides bugs that only show up when real carriers are involved. I'm currently setting up a nightly test suite that uses actual virtual numbers to test our verification flow end-to-end. Still iterating on which providers work best for this — will share the results once I have enough data.


Phone-based auth is one of those features that's deceptively simple. The happy path takes a day to build. Everything else takes months. If you're about to add SMS verification to your app, I hope this saves you some of the surprises I ran into.

If you've been through this — especially the carrier filtering and international delivery issues — I'd genuinely like to hear how you handled it. My solutions work, but they're held together with more duct tape than I'd like to admit.

Top comments (0)