# Routing an outbound list by line type

_2026-09-10 · Boundstone (https://boundstone.io/blog/route-outbound-by-line-type)_


Most outbound teams treat a phone list as one channel and then wonder why the SMS numbers underperform. A list is at least three channels, and the split is knowable before the first touch.

One pass over the file gives you `line_type` on every row. What you do with the third bucket is what decides whether the exercise was worth anything.

## What `line_type` actually tells you

`POST /v1/verify/phone` returns `line_type` alongside `valid`, `country` and the E.164 form. The value comes from Google's libphonenumber metadata: the *designated* type of the range the number sits in, according to that country's numbering plan.

That is a genuinely strong signal and a bounded one. It tells you what a range is for. It does not tell you what the network currently thinks about one specific number in it — `carrier_lookup`, `ported_status` and `hlr_liveness` sit in `checks.not_performed` by default, and the response says so on every call.

Where numbering plans separate mobile and fixed ranges — which is most of the world — you get a clean split with no ambiguity to manage.

## The three buckets

**`mobile` → SMS is safe.** The range is designated for mobile service. This is where a text campaign should live.

**`fixed_line` → calls only.** Texting these spends the message and buys nothing. Route them to voice, or drop them if voice is not a channel you run.

**`fixed_line_or_mobile` → its own bucket, with a rule.** North America is the hard case: the NANP shares ranges between cell and landline, so the same prefix can be either. The API returns the ambiguous value rather than picking, because picking would be a guess dressed as an answer.

There are also `toll_free`, `voip` and `premium_rate` values worth pulling out — toll-free numbers are switchboards rather than people, and VoIP is disproportionately common in throwaway signups.

## The ambiguous bucket is the whole exercise

If you fold `fixed_line_or_mobile` into mobile, you have re-created the problem you were segmenting to solve, and you have done it invisibly — the rows look routed. If you fold it into fixed, you throw away real mobile numbers.

Two defensible rules:

1. **Route it to voice.** Slower per contact, but no wasted messages and no deliverability risk. This is the right default for most teams.
2. **Enrich only that segment.** Send `hlr:true` on those rows and Boundstone runs a live HLR dip at 5 credits on a paid plan. `carrier_lookup`, `ported_status` and `hlr_liveness` move into `checks.performed`, and you get the current carrier and a `reachable` value that reads `true` only when the network answered CONNECTED. If the network cannot resolve the number the dip abstains and the credits are refunded.

Dipping only the ambiguous segment is usually far cheaper than dipping the list, because in most countries that bucket is small.

## Doing it over a whole file

`POST /v1/bulk/phone` takes the file — 250 rows per job on the free tier, 10,000 on a paid plan, one credit per row and refunded on any row that errors. Pull `results.csv` and segment on the `line_type` column.

Write `e164` back as your canonical phone field while you are there. Deduplicating on formatted numbers is how the same prospect ends up in two sequences.

## What this does not settle

Segmentation is hygiene, not compliance. `line_type` does not tell you whether you are permitted to contact a number — DNC scrubbing and consent are separate steps with separate tools, and nothing here is legal advice.

It also does not tell you the line is live. A perfectly valid `mobile` can have been disconnected last year and will still segment as mobile, because the numbering plan has not changed. That is what the live dip is for, and what `not_performed` is telling you when you have not asked for one.

## The short version

- **A phone list is at least three channels.** `mobile` to SMS, `fixed_line` to calls, ambiguous to its own deliberate bucket.
- **`line_type` is numbering-plan metadata, not a network reading.** It describes the range, not today's status of one number in it.
- **Never fold `fixed_line_or_mobile` into mobile.** The API abstained on purpose; treating the abstention as an answer re-creates the problem invisibly.
- **Enrich the ambiguous bucket, not the list.** `hlr:true` at 5 credits, refunded on abstain, applied to a small segment beats dipping everything.
- **Pull `toll_free` and `voip` out too.** Switchboards are not people, and VoIP skews heavily toward throwaway signups.
- **Segmentation is hygiene, not compliance.** DNC and consent remain separate steps.
