# When does an HLR lookup pay for itself? Break-even math for SMS and OTP costs

> A simple cost model for pre-send checks: when an HLR, MNP or carrier lookup costs less than the SMS it saves, with formulas and a worked table.

Canonical: https://mobilevalidate.com/blog/when-does-an-hlr-lookup-pay-for-itself · Last updated: 2026-09-30

![A balance scale with one small HLR check token on the rising pan and a stack of risk-marked envelopes on the heavy pan, beside a break-even chart and three rows priced at zero.](https://mobilevalidate.com/images/blog/when-does-an-hlr-lookup-pay-for-itself.svg)

*A pre-send check pays off once the share of bad numbers times the cost of a wasted send exceeds its price.*


By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-09-30 · Category: Deliverability · Tags: HLR, SMS costs, OTP, Pre send checks, List hygiene, Deliverability

An HLR lookup pays for itself when the share of bad numbers you check, multiplied by the money a wasted send would cost, is larger than the price of the check. At $0.005 per lookup and $0.08 wasted per bad number, the break-even point is about 6% bad numbers. Below that rate, skip the check or use a cheaper one.

That one sentence hides a few details that change the answer a lot: how many segments and resends a bad number triggers, which answers are billed, and whether a cheaper check would catch the same waste. This guide turns them into a formula you can fill in with your own numbers.

## What does a wasted SMS really cost?

A wasted send is every message you pay for that can't reach a person. The price per message is only the start. Count everything a bad number sets off:

- **Segments.** A text longer than one segment is billed per segment. Long campaign messages or messages with non-Latin characters often use two or three.
- **Resends.** In an OTP flow, a user who doesn't get the code taps "resend", often more than once. Each tap is another paid message to the same bad number.
- **Fallbacks.** Some verification setups retry by voice call after a failed SMS. That call is usually more expensive than the text.
- **Downstream costs.** A support ticket, an abandoned sign-up or a campaign message that never lands all have a cost, even if it's harder to measure.

Write it as one number per bad contact:

```text
W = price per segment × segments × sends per bad number + fallback cost
```

Keep downstream costs out of `W` at first. If the check already pays off without them, you don't need to argue about how much a lost sign-up is worth.

## What is the break-even formula?

A pre-send check pays for itself when the expected saving per checked number is higher than the expected cost per checked number:

```text
p × W  >  c × q
```

- `p` is the share of checked numbers that are bad in a way the check detects.
- `W` is the wasted cost per bad number, from above.
- `c` is the price of one conclusive check.
- `q` is the share of checks that come back conclusive and are billed.

Solve for `p` and you get the break-even bad-number rate:

```text
p* = (c × q) / W
```

If your real bad-number rate is above `p*`, the check saves money. If it's below, it costs money.

The `q` term matters more than it looks. With MobileValidate you're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Badly formatted input is marked `invalid_number` and never checked, repeats of the same number in a list are marked `duplicate`, and cache hits inside the freshness window come back with `billed: false`. On a messy list, a noticeable share of rows costs nothing, which lowers the effective price per row. For a conservative first estimate, set `q = 1`.

## Which check should you price in?

Three checks remove different kinds of waste at different prices. Prices below are from the live catalog on 30 September 2026. See [pricing](/pricing) for current prices.

| Check | Price per conclusive check | What it removes from a send |
|---|---|---|
| [HLR lookup](/services/hlr-lookup) | $0.005 real time and bulk | Numbers not assigned (`invalid`), and numbers not reachable right now (`unreachable`) |
| [Carrier lookup](/services/carrier-lookup) | $0.002 real time, $0.001 bulk | Fixed lines and other line types that can't take SMS, from `line_type` |
| [MNP lookup](/services/mnp-lookup) | $0.001 real time and bulk | Nothing directly; it shows the current network so you can route a ported number correctly |

The first rule of pre-send economics follows from this table: **use the cheapest check that answers the question behind your waste.** If most of your waste is landlines in a form that accepts any number, a carrier lookup at a fifth of the HLR price finds it. If your waste is numbers that were disconnected since you collected them, only a live network query sees that. The difference between the two is covered in [HLR vs MNP vs number validation](/blog/hlr-vs-mnp-vs-number-validation).

The MNP lookup doesn't fit the break-even formula at all. Its value is in routing: sending a ported number over the route for its current network, which can change your price per message or your delivery rate. Measure that saving per message against $0.001, not against a removed send. The [SMS routing use case](/use-cases/sms-routing-mnp-hlr) shows how routing teams use it.

## What does the math look like in practice?

The scenarios below use **illustrative assumptions**, not market prices. SMS rates vary widely by destination, sender and contract, so replace them with the rates on your own invoice. All four use `q = 1`.

| Scenario (assumed values) | W per bad number | Check | Break-even p* |
|---|---|---|---|
| Domestic OTP, $0.008 per SMS, 1 send | $0.008 | HLR $0.005 | 62.5% |
| International OTP, $0.05 per SMS, 1.5 sends on average | $0.075 | HLR $0.005 | 6.7% |
| Campaign to an old list, $0.04 per segment, 2 segments | $0.08 | HLR $0.005 | 6.25% |
| Web-form list with landlines, $0.04 per SMS, 1 segment | $0.04 | Carrier $0.001 (bulk) | 2.5% |

Read the table row by row:

- **Cheap domestic OTP.** Almost no list has 62% bad numbers, so an HLR check before every domestic code would cost more than it saves. Rate limits and format validation do the job here.
- **Expensive international OTP.** A 6.7% bad rate is realistic for first-time sign-ups on some destinations, especially once resends are counted. The check is close to break-even or better, and it adds a second benefit covered below.
- **Old campaign list.** Lists decay as people give up numbers. Our post on number recycling and list decay explains why. A list that hasn't been touched for a year or two can easily pass 6%.
- **Landlines in a web form.** Even a low share of fixed lines justifies a bulk carrier lookup, because it costs so little per row.

## How do you estimate your bad-number rate first?

Don't guess `p`. Measure it on a sample before you check a whole list.

1. **Run the free estimate.** `POST /v1/jobs/estimate` takes the same body as a bulk job and counts valid, invalid, duplicate, cached, unsupported and suppressed rows, plus the maximum cost, without checking or charging anything. The invalid and duplicate share is waste you remove for free. See [bulk jobs](/docs/bulk-jobs).
2. **Check a random sample.** Pick a few hundred to a thousand valid numbers at random, not the first rows of the file, which are often the oldest or newest contacts. Run the check you're considering on them.
3. **Compute `p` from the sample.** Count the answers that would stop a send (`invalid`, and for OTP `unreachable`) and divide by the conclusive answers.
4. **Allow for sampling error.** With 1,000 sampled numbers and a measured rate of 8%, the 95% margin of error is roughly ±1.7 percentage points. If `p*` sits inside that margin, you're close to break-even and the decision is a matter of taste.
5. **Decide per segment.** Old contacts, imported lists and contacts from specific countries often have very different rates. Check the segments above break-even and skip the rest.

One caution for bulk pricing: the bulk price for a check applies per country when a job has enough numbers from that country. Smaller groups are priced at the real-time price, and the estimate shows them in its breakdown. Group your list sensibly before you rely on the lower rate.

## How does the math change for OTP?

For one-time passcodes, the saving isn't only the SMS you don't send. It's also the time the user doesn't spend waiting.

An `unreachable` HLR answer means the phone is switched off or out of coverage now. For a marketing message that's a reason to hold back and retry later. For an OTP, it's a reason to offer another channel straight away: a voice call, an authenticator app, or a messenger the user chose. That turns a likely failed sign-up into a completed one. If you add even a small value for a rescued sign-up to `W`, the break-even rate drops.

Three rules keep OTP checks economical:

- **Check only paths that send.** Run the lookup after the form is valid and just before the code is sent, never on page load or on every keystroke.
- **Check first-time numbers.** A number that verified a code last week doesn't need a new HLR lookup today. Store the result and the date with the account.
- **Let the cache absorb resends.** Repeat lookups of the same number inside the freshness window are free cache hits. When you need a fresh reachability answer, `max_age: 0` forces one, and that check is billed.

Checks don't replace fraud controls. If bots are triggering codes to numbers they profit from, the right tools are rate limits, a country allow-list and line-type rules. Our guides to [SMS pumping](/blog/sms-pumping-how-it-works-and-how-to-stop-it) and [checks before sending a code](/blog/otp-fraud-prevention-checks-before-sending-a-code) cover them. The cost model here is about honest users with bad numbers.

## How does the math change for campaigns?

Campaigns go to people who opted in, often in large batches, so the per-message cost and the timing are different.

- **Count every segment.** A two-segment message doubles `W` and halves the break-even rate.
- **Count every send.** If you send to the same list monthly, one check removes a bad number from every future send until the list is refreshed. Multiply `W` by the number of sends until the next check.
- **Treat `unreachable` carefully.** A phone that is off during the check may be on an hour later, and the network may still deliver a message stored for later delivery. Count only `invalid` as certain waste in a campaign, and hold `unreachable` numbers for a retry rather than deleting them.
- **Combine with suppression.** Opt-outs and your own do-not-contact records come first. They're free and they matter legally.

Delivery receipts can refine your estimate afterwards, but they arrive after you've paid. The trade-off between the two is covered in [SMS delivery receipts vs HLR lookup](/blog/sms-delivery-receipt-vs-hlr-lookup), and the full cleaning workflow in [how to clean a phone number list in bulk](/blog/how-to-clean-a-phone-number-list-in-bulk).

## When should you not run a check?

A check that doesn't pay for itself is just another cost. Skip it when:

- your SMS rate for the destination is lower than or close to the check price, and a bad number triggers only one send;
- the number was verified by the user recently, for example a code entered successfully in the last few weeks;
- the list is fresh, collected with confirmed consent days ago, and your sample shows a low bad rate;
- the result wouldn't change what you do. If you'd send the message anyway, don't pay to learn the answer.

The [SMS cost reduction use case](/use-cases/sms-cost-reduction) shows the same checks from a product angle.

## What are the key takeaways?

- A pre-send check pays for itself when `p × W > c × q`: bad-number share times wasted cost per bad number, against check price times billed share.
- Count segments, resends and fallbacks in `W`. They often move the break-even rate from impossible to realistic.
- Use the cheapest check that finds your kind of waste: carrier lookup for line types, HLR for disconnected or unreachable numbers, MNP for routing.
- Measure `p` on a random sample, after a free estimate removes invalid and duplicate rows.
- Unknown answers, invalid input, duplicates and cache hits are not billed, which lowers the real price per row.
- Skip checks on cheap domestic sends and recently verified numbers, where they cost more than they save.

## Frequently asked questions

### When is an HLR lookup worth its price?

When the share of bad numbers multiplied by what a wasted send costs you is higher than the price of the check. With an HLR lookup at $0.005 and a wasted send costing $0.08, the check pays for itself once more than about 6% of the numbers you check are bad.

### Do I pay for every HLR lookup?

No. You pay only for conclusive answers: reachable, unreachable and invalid (not assigned). Unknown answers, timeouts, unsupported countries, badly formatted input, duplicates and cache hits inside the freshness window are free.

### Should I check numbers before every OTP?

Not always. For cheap domestic SMS and for numbers that verified successfully recently, the check can cost more than it saves. It pays off on expensive destinations, on first-time numbers, and on paths where one bad number triggers several resends.

### HLR, MNP or carrier lookup: which one saves the most?

They answer different questions. The HLR lookup finds numbers that are not assigned or not reachable now. The carrier lookup finds landlines and other lines that can't take SMS. The MNP lookup shows the current network of a ported number, which helps routing rather than removing numbers. Pick the cheapest check that answers the question behind your waste.

### How do I know my bad-number rate before paying for a whole list?

Check a random sample first. A few hundred to a thousand numbers give a usable estimate, and the free job estimate already shows how many rows are invalid or duplicate without checking anything.
