← Blog

How to validate leads before they hit your CRM

Run every lead through a bulk email and phone check before import, so the CRM only ever meets normalized, segmented rows — and never the provably-bad ones.

Contents

Every bad row you import is a row you pay for later — a sequence that bounces, a dial that dead-ends, a rep's afternoon spent on a number that was never a number. The cheapest place to catch junk is before it lands in the CRM, while it's still an anonymous line in a CSV and not a "contact" with a lifecycle stage and an owner. This is a hygiene step, not a magic wand. You're removing the provably-bad, not the merely-stale. Here's how to run email and phone through a bulk check at import, and exactly where the honesty line sits.

Decide what "bad" means before you import

Junk comes in a few flavors, and they are not equal:

  • Syntax-broken emails (john@, jane at company dot com) — provably bad.
  • Disposable-domain emails (@mailinator.com) — provably low value.
  • Role accounts (info@, sales@) — not a person; segment or drop.
  • Impossible phone numbers — wrong length, no such area code — provably bad.
  • Valid numbers on the wrong line type — segment, do not SMS a fixed line.

Boundstone's job is the first gate: drop the provably-bad, normalize the survivors, tag them so your CRM starts clean. It does not tell you whether a well-formed inbox actually receives mail, or whether a valid number is switched on right now. Hold that thought — it's the whole point of the honesty contract below.

Run the list through bulk, before it touches the CRM

Bulk takes a raw CSV and hands back a job id. One column, one value per line:

phone
+16504472983
650-447-2983
5551234
+442071838750

Post it:

curl -X POST https://api.boundstone.io/v1/bulk/phone \
  -H "Authorization: Bearer bs_live_YOUR_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @phones.csv

You get back HTTP 202 and a job_id. Pull the results when it finishes:

curl https://api.boundstone.io/v1/bulk/JOB_ID/results.csv \
  -H "Authorization: Bearer bs_live_YOUR_KEY"

Email is the same call at /v1/bulk/email. Free tier runs 250 rows per job, paid runs 10k. One credit is reserved per row and refunded for any row that errors — that's a row the system couldn't process, not a row that comes back invalid. Learning a number is junk is a successful check, so it costs a credit; a row that failed to process does not. And if you'd rather not touch a terminal, the dashboard has a CSV upload and paste-box that runs the identical job.

Read the fields, then drop, normalize, or segment

A single-row phone response shows you what each column means:

{
  "valid": true,
  "e164": "+16504472983",
  "country": "US",
  "line_type": "FIXED_LINE_OR_MOBILE",
  "national_format": "(650) 447-2983",
  "checks": {
    "performed": ["format", "region", "line_type_metadata"],
    "not_performed": ["carrier_lookup", "ported_status", "hlr_liveness"]
  }
}

valid: false drops the row. Write e164 back as your canonical phone field — store +16504472983, not (650) 447-2983, so dedupe and dialers agree. Segment on line_type. When it comes back FIXED_LINE_OR_MOBILE, the numbering plan genuinely can't distinguish the two — common across North America — so don't guess a channel; route it somewhere that tolerates either, or fall back to a call.

Email works the same way:

{
  "valid_syntax": true,
  "domain": "example.com",
  "mx_found": true,
  "disposable": false,
  "role_account": false,
  "free_provider": false,
  "checks": {
    "performed": ["syntax", "mx", "disposable_list", "role_list"],
    "not_performed": ["smtp_mailbox", "catch_all"]
  }
}

Drop on valid_syntax: false, disposable: true, or mx_found: false — a domain with no MX can't receive mail at all. Segment role_account: true away from personal outreach. free_provider is a scoring flag, not a reason to drop; a Gmail address is still a person.

The line between provably-bad and merely-stale

checks.not_performed is not a footnote. It's the contract, and it's what keeps this honest.

For phone, not_performed is carrier_lookup, ported_status, hlr_liveness. So valid: true means well-formed and possible for its region with a designated line type. It does not mean the number is in service, reachable, or answered. A long-disconnected mobile still validates. And line_type is libphonenumber metadata — the number's designated type in its numbering plan, not a live network check — so a VoIP line ported onto a mobile range still reads as mobile. To know a line is live and who carries it today, you need a live carrier/HLR lookup — a paid opt-in via hlr:true at 5 credits, refunded when the network cannot answer.

For email, not_performed is smtp_mailbox and catch_all. Syntax is clean and the domain publishes MX, so mail can route there — but Boundstone doesn't knock on the mailbox or detect a catch-all. A perfectly formed address at a real domain can still bounce.

One more line worth stating plainly: this is data validation, not a Do-Not-Call scrub and not legal advice. Dropping impossible numbers is hygiene. TCPA and DNC obligations run through the official registries (the US National Do Not Call Registry, state lists) and your counsel, not through this API.

Wire it into the import

  1. Export the raw list to CSV, one column per file.
  2. POST each column to /v1/bulk/phone and /v1/bulk/email — or upload in the dashboard.
  3. Pull results.csv for each job.
  4. Drop valid: false phones and valid_syntax: false / disposable: true / mx_found: false emails.
  5. Write e164 back as the canonical phone field.
  6. Tag line_type and role_account so your routing rules can read them.
  7. Import the survivors.

Going deeper on either side: bulk email validation over CSV and cleaning a phone list before calling. For a one-off address, the free email validator runs the same syntax, MX, disposable, and role checks in the browser.

The short version

  • Validate at import, not after. The CRM should never meet a provably-bad row.
  • Bulk-POST your CSVs to /v1/bulk/phone and /v1/bulk/email; one credit per row, refunded on error.
  • Drop, normalize, segment. Drop the invalid and impossible, normalize phones to e164, segment by line_type and role_account.
  • checks.not_performed is honest: no mailbox knock (smtp_mailbox, catch_all), and no live-line check by default (carrier_lookup, ported_status, hlr_liveness). You remove the provably-bad, not the merely-stale.
  • Not a DNC scrub and not legal advice — that is the registries and your counsel.

Frequently asked questions

What does it mean to validate a lead before it hits my CRM?

Lead validation checks whether the contact details a lead submits are well-formed and plausible before they pollute your database. Boundstone validates phone numbers (format, region, and line-type metadata, normalized to E.164), emails (syntax, MX records, disposable-domain and role-account flags), and IP addresses (format, version, and range classification). Every response lists exactly which checks were performed and which were not, so you always know what a result does and does not prove. That lets you reject the obviously broken records at the door without pretending you have confirmed more than you actually have.

Does a valid phone number mean the number is live and safe to call?

No, and this is where honest validation matters most. A valid result means the number is well-formed, carries recognizable line-type metadata, and normalizes to E.164; it does not confirm the number is currently live, in service, reachable, or tied to a specific carrier, and it is not a DNC or TCPA compliance check. Boundstone reports carrier lookup, ported status, and HLR liveness as not performed by default (live carrier checks are a paid opt-in via hlr:true), so you never mistake formatting hygiene for a dialing guarantee. Treat validation as removing the clearly bad numbers, and keep your own consent and compliance process for the calls themselves.

Can I validate an entire list of leads at once before importing?

Yes. Boundstone's bulk endpoints accept a CSV of phone numbers, emails, or IP addresses (up to 250 rows per job on the free tier and 10,000 on paid), so you can clean an export before it ever reaches your CRM. Credits are reserved per row and automatically refunded for any row that errors, so a malformed file never quietly drains your balance. The free tier gives you 250 credits a month with no card required, and credits never expire.

Thomas Tsui

Founder of Boundstone — building phone, email and IP validation you can actually verify.

One honest API for email, phone and IP — every response lists what it checked and what it didn't claim to. Free tier: 250 credits/month, no card, credits never expire.

More from Boundstone — API documentation · Benchmark methodology · The benchmark series · Buyer's checklist