The silent failure nobody talks about
You send 50,000 messages. Your platform reports a 94% delivery rate. You feel good. What it doesn’t tell you is that the remaining 6% didn’t bounce with a useful error code. They just vanished. Some of those were landlines that your carrier silently discarded, and some were text-enabled landlines that absolutely could have received your message if your tool had bothered to check.
I’ve seen this pattern on campaigns across retail, healthcare reminders, and appointment confirmations. The platform marks the message as “sent,” the carrier eats it, and you never know. You’ve paid for the send, lost the contact attempt, and have no idea which numbers to retry via voice or reassign to a call-back queue.
Why most tools don’t catch this
The default behavior of virtually every SMS platform is to accept a number, pass it to the carrier, and move on. Line type checking isn’t built into the send path. It’s an optional, often paid enrichment that most teams skip because “it adds friction” or because nobody on the team has actually watched a campaign fail quietly and traced it back to landlines.
The thing is, not all landlines behave the same way. A traditional PSTN landline will reject an SMS silently or return an error that your platform probably swallows. But a growing number of landlines, particularly VoIP-based business lines provisioned through carriers like Bandwidth or Syniverse, have been explicitly enabled for SMS by the subscriber. Those numbers have a real inbox. You can reach them. You just need to know which ones they are before you send.
What the carrier DIP actually tells you
When you run a number through CheckThatPhone’s /v1/lookup endpoint, the standard response gives you nanpType, which will return mobile, landline, or not-mobile. That alone is enough to filter obvious landlines out of a mobile-only send. But if you stop there, you’re leaving text-enabled landlines on the table.
To go deeper, include landlineSmsLookup: true in your request body. That triggers a live carrier DIP specifically for SMS capability on non-mobile lines. The response adds three fields:
dipMessagingLookup: confirms whether the lookup was actually performeddipMessagingEnabled:"true"or"false"indicating whether that landline is registered to receive SMSdipMessagingProvider: the name of the messaging provider handling that line
A request looks like this:
curl -X POST https://api.checkthatphone.com/v1/lookup \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{
"phone": "2025550178",
"landlineSmsLookup": true
}'
If nanpType is landline and dipMessagingEnabled is "true", send the SMS. If dipMessagingEnabled is "false", route that contact to a voice callback queue or skip them entirely. That’s the whole decision tree.
The cost math is not complicated
Let’s say you’re paying $0.0075 per outbound SMS segment and your list has 10,000 numbers. A conservative estimate puts landlines at 8 to 12 percent of a typical mixed consumer/business list. Call it 1,000 numbers.
Of those 1,000 landlines, maybe 15 to 20 percent are text-enabled. So roughly 800 are traditional landlines that will silently fail, and around 200 are reachable.
Without a lookup, you spend $7.50 on those 800 dead sends. You also miss 200 contacts who could have received your message.
CheckThatPhone’s lookup pricing (see /pricing) runs well under a cent per lookup for most volume tiers. On 10,000 numbers, the total enrichment cost is a fraction of what you’re burning on undeliverable sends, and you recover the 200 text-enabled landlines on top of that.
The math only gets better as list size grows, and much better if your SMS cost per message is higher than the baseline (think A2P 10DLC fees on top of carrier costs).
Building the routing logic
In practice, I’d structure this as a pre-send enrichment step rather than something you run inline during campaign execution. Enrich the list, tag each number with its routing decision, then hand the segmented list to your send platform.
For real-time signups, you use the /v1/lookup endpoint per number at the point of collection. A user submits their phone number, you call the API, and within a couple hundred milliseconds you know whether to enroll them in the SMS list, the voice-callback list, or flag the number for review.
For existing lists or large imports, use the Bulk CSV feature in the CheckThatPhone dashboard (Dashboard, then Bulk CSV). Upload your list, enable the landline SMS add-on for the job, and get back a result file with every row annotated. It deduplicates automatically, so you’re not paying twice for numbers that appear more than once. Results and uploaded files are deleted within 24 hours of delivery, which matters if your list includes any PII.
Once you have the enriched file, your routing logic is a simple conditional on dipMessagingEnabled:
nanpType: mobilegoes to the SMS listnanpType: landlineanddipMessagingEnabled: "true"goes to the SMS listnanpType: landlineanddipMessagingEnabled: "false"goes to the voice queue or suppression listnanpType: not-mobileanddipMessagingEnabled: "false"gets suppressed
If you want to go further, dipCarrier, dipCarrierType, and dipCarrierSubType tell you who’s actually carrying the number post-port. dipPorted tells you whether the number has moved carriers since it was issued. A ported mobile that now shows a VoIP subtype is a different deliverability risk than a native mobile number, and that context matters when you’re debugging delivery issues at scale.
One more thing before you send
If your campaign touches any list you didn’t build from confirmed opt-ins, run the litigator scrub. Add litigatorFilter: true to your request. The response will include litigator, litigator_type, and litigator_name. TCPA litigation is not a theoretical risk. Professional plaintiffs actively collect numbers, wait for unsolicited messages, and file. A single case costs more than every lookup you’ll ever run. See /use-cases/tcpa-litigator-scrub for how the scrub works in practice.
For broader list hygiene, including deactivated numbers (the deactivationDate field) and blacklisted numbers (blackList, reason, action), the contact list hygiene use case is worth reading before your next import.
What to do right now
Pull your last campaign’s send list. Run a sample of 500 numbers through the API with landlineSmsLookup: true. Count how many come back as nanpType: landline. Then check dipMessagingEnabled on those. I’d bet you find at least 5 percent pure landlines and a non-trivial slice of text-enabled ones you’ve been missing.
That number is your baseline. From there, the fix is just a routing rule and a pre-send enrichment step you run once per list. It’s not a platform migration or a big engineering project. It’s a lookup and a conditional.