Why Your SMS Campaign Is Silently Dropping Landlines, and How to Detect Text-Enabled Ones

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.

It’s a common pattern on campaigns of every kind, from retail promotions to appointment reminders. 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 many SMS platforms 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 many 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 landline will reject an SMS silently or return an error that your platform probably swallows. But some landlines, often business lines, have been text-enabled through a messaging provider. About 7% of landlines we check are SMS-capable. Those numbers have a real inbox. You can reach them. You just need to know which ones they are before you send. (For how a text actually reaches a landline, see how text-to-landline routing works.)

What does a carrier dip tell you about a landline?

It tells you the number is a landline, and with one add-on, whether that landline can take a text. When you run a number through CheckThatPhone’s POST /v1/lookup endpoint (docs), the standard response gives you dipCarrierType from a live carrier dip at lookup time (mobile, landline, invalid, or undefined), plus nanpType from numbering-plan data. That alone is enough to filter landlines out of a mobile-only send (see the line type lookup API). 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 runs a landline SMS lookup on landline numbers only (mobiles skip it and aren’t charged for it). The response adds these fields:

  • dipMessagingLookup: "true" when the lookup actually ran, which means the number is a landline. Absent for mobiles.
  • dipMessagingEnabled: "true" or "false", whether messaging is enabled for that landline
  • dipMessagingProvider: the name of the messaging provider serving the line, when there’s a match
  • dipMessagingRefId and dipMessagingCountryCode: the provider’s reference ID and country

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 dipCarrierType is landline and dipMessagingEnabled is "true", send the SMS. If dipMessagingEnabled is "false", route that contact to a voice callback queue or suppress it. That’s the whole decision tree.

Does the lookup pay for itself?

It depends on how often you message the list. Say you pay $0.0075 per outbound SMS segment (check your provider’s current rate), your list has 10,000 numbers, and 10% of them are landlines: 1,000 numbers. Your list may run higher; across all our lookups, about 32% of numbers are non-mobile (landline or VoIP).

With about 7% of landlines SMS-capable, roughly 70 of those 1,000 can take a text and 930 can’t. One send to the 930 wastes about $7, and the 70 reachable landlines never get flagged as reachable.

Validating the list costs $30 on the 10K plan, plus 1 credit ($0.003 in overage) for each of the 1,000 landlines if you turn on the landline SMS lookup. So on a single send, the lookup costs more than the SMS it saves. The math flips when you validate once and send many times: a list you message weekly wastes about $30 a month on those landlines, and that repeats every month the list stays dirty. It also improves with volume, since the per-credit price drops on larger plans (see /pricing), and with higher per-message costs such as A2P 10DLC carrier fees.

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 make one synchronous call, and 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 uploader in the CheckThatPhone dashboard. 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. The upload and the result file are hard-deleted within 24 hours, which matters if your list includes any PII.

Once you have the enriched file, your routing logic is a simple conditional. The smsEligible field does most of it for you: it’s "true" for an active mobile, and for a landline confirmed SMS-capable when the landline SMS lookup is on. It’s omitted otherwise.

  • smsEligible: "true" goes to the SMS list (mobiles and text-enabled landlines)
  • dipCarrierType: landline and dipMessagingEnabled: "false" goes to the voice queue or suppression list
  • Everything else (dipCarrierType of invalid, a recent deactivationDate, a blacklist hit) 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 collect numbers, wait for unsolicited messages, and file, and statutory damages start at $500 per message. See /use-cases/tcpa-litigator-scrub for how the scrub works in practice, and litigator scrub vs. DNC registry vs. internal opt-out for how it fits with your other suppression lists.

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 dipCarrierType: landline. Then check dipMessagingEnabled on those. Across all our lookups, about 32% of numbers are non-mobile and about 7% of landlines are SMS-capable; your list will have its own mix, and that’s what the sample tells you. For more background on the line types themselves, see understanding line types.

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.

Run your own numbers through it

Carrier, line type, porting and deliverability for US and Canadian numbers in one call. The Free plan includes 500 credits a month.