The local business list-building system (for cold email)
How to use this: open Claude Code (or any coding agent), paste this ENTIRE document, and say "build this". It builds the whole system. Every rule here comes from a real run, and the numbers are what we measured.
Results that came out of it, same 100 businesses, same verifier, same day: verified business emails found out of 85 live businesses: this system 49, Mapsdata 32, LeadMarina 26. Email of a named person (not info@): 42 / 24 / 18.
1. The Claude Code build prompt
You are building a pipeline that turns "a type of local business + a city" into a CSV of businesses with ONE verified email each, the owner's name where possible, and the phone.
- Python, one CLI, each stage saves its results to disk so a crash or re-run never pays twice.
- Input:
--query "electrician" --query "electrical contractor" --bbox west,south,east,north --state CO - Output CSV columns: name, category, address, city, state, phone_e164, website, domain, place_id, rating, reviews, source, owner_name, email, email_status, email_route, social_facebook, social_instagram, social_linkedin.
- Stages, in order: preflight → discover (part 2) → add licence lists (part 3) → filter (part 2) → find the email (part 4) → verify (part 5) → wrong-business check (part 6) → store and reuse (part 5) → export.
- Preflight, before anything else: read the balance of every paid API the run uses (verifier, second verifier, email finder, proxy). If a required one is at zero, STOP with an error naming it. Never log "skipped" and carry on: a dead vendor silently cuts your coverage and you won't notice.
- Write a test for each stage before the code, with the network mocked.
2. Every business in a city from Google Maps (not just the 120 it shows)
Google Maps stops at ~120 results for one search, so one search per city misses most businesses. Tile the city instead.
- Endpoint: the same request the Maps web app makes,
https://www.google.com/search?tbm=map&...with the map centre, zoom and viewport in thepbparameter. It returns 20 places per page. (If you'd rather not build this, any Google Maps scraping API works; the tiling still applies.) - Tiling: project the bounding box at zoom 14, split it into viewport-sized tiles (800 px tall), 20% overlap between tiles, cap at 500 tiles.
- Per tile: run each search term, follow up to 3 pages (offset 0, 20, 40).
- Dedupe on Google
place_id. Drop results outside the box or in the wrong state. - Proxy: a rotating residential proxy is mandatory at scale. Without one Google rate-limits you after a few tiles (HTTP 429). With one we ran 15,300 requests with 0 errors.
- Politeness: at least 100 ms between requests, 4 retries with backoff on 429/5xx, 4 to 16 tiles in parallel.
- Search terms: the trade's own nouns only ("electrician" + "electrical contractor", "plumber" + "plumbing contractor"). Adding "company", "services" or "repair" returned EV chargers, auto shops and utilities: zero extra electricians across 5 extra phrasings.
- Keep per place: name, all categories, address, phone, website, rating, review count, lat/lng, place_id, cid.
- Then FILTER before you pay for anything: keep or drop on Google's own category labels
(allow list + deny list) plus a name regex for obvious junk (supply stores, hardware
stores, inspectors, franchise HQs). Only send genuinely unclear rows to an LLM for an
in / out / uncertain call. Do NOT filter on a B2B database's industry tag: one database
held 4,270 companies in a city and tagged 28 of them correctly. Keep dropped rows in the
output marked
prefiltered, never silently delete them.
Benchmarks: Salem OR property managers, 263 unique from tiling vs 200 from a single search; Greater London dentists, 1,976 unique in 28 seconds.
3. The free state licence lists (businesses Google Maps doesn't show)
Many US states publish every licensed contractor as free open data. These businesses often have no Google Maps listing, so they get far less cold email.
- Colorado (no key needed):
https://data.colorado.gov/resource/7s5z-vewr.jsonfilterlicensetype='EC'(electrical contractor) andcity='Fort Collins', keeplicensestatusdescriptionstarting with "Active". Fields: entityname, city, state, mailzipcode, licensetype, licensenumber. - Texas (TDLR):
https://data.texas.gov/resource/7358-krk7.json, includes phone and address. - Washington and Oregon publish theirs with phone and address too. Search
"
contractor license data download" for others. - These are Socrata APIs: page with
$limitand$offset, filter with$where. - Fuzzy-match against your Maps list (lowercase, strip punctuation and LLC/Inc/Co) and keep
only the ones Maps didn't have, tagged
source=registry. - Registry rows have no website: Google "
" and take the first result that isn't a directory (Yelp, BBB, Facebook, Angi, Houzz, Yellowpages, Mapquest, Bizapedia).
Benchmark: Colorado had 106 active electrical contractors in Fort Collins; ~75 of them were not on Google Maps at all.
4. The 3-step email finder (cheapest first, stop at the first verified email)
Run it in tiers. Verify each tier in ONE bulk call across all businesses; only businesses still without a deliverable email move to the next tier. Guesses then only get verified where nothing better exists.
Step 1: their own website + a B2B database
- Crawl the homepage plus the contact/about pages it actually links to (max 3 pages, 12 s
timeout each, 12 to 20 sites in parallel). Extract plain addresses, mailto: links and
Cloudflare-obfuscated ones (data-cfemail, XOR-decode with the first byte).
- Keep an address only if it's at the business's own domain, or a free-mail address (gmail
etc.) that appears as a mailto: on their own site.
- This alone found a verified email for about half of all businesses, free.
- Then look the domain up in a B2B contact database (we use QuickEnrich).
Step 2: find the owner, then their email
- Give an LLM the crawled page text and ask for the owner / founder / president / principal
with the exact quote. Reject any name not literally in the text.
- No name on the site: Google site:linkedin.com/in "<company>" owner OR founder OR president
and accept a name only when the result names the company.
- Email finder on name + domain (we use DiscoLike email-find, billed only on a proven hit).
- No finder: generate first.last, flast, first, firstlast, f.last, firstl, last and verify.
Step 3: role guesses, last - info@, office@, contact@, admin@, sales@ at the domain.
The catch-all trap: on catch-all domains (domains that accept mail for any address), email finders return a pattern GUESS that looks exactly like a proven hit. We checked 22 of those guesses: 21 were wrong. Never accept a finder result flagged as a catch-all pattern.
5. Which emails to never pay to verify (and never paying twice)
Verification:
- Primary verifier in BULK mode only (we use Reoon bulk; never its quick mode, which misses
Office 365 mailboxes). Save the job id before polling so a crash re-pulls for free.
- safe = deliverable. invalid / disabled = drop.
- catch_all / unknown on a NAMED address (jane@): send to a second verifier
(BounceBan or OrbiSearch).
- catch_all on a ROLE address (info@, office@): do NOT pay to verify it. Even the best
verifier can't detect those bounces: tested against 48 addresses that had actually bounced,
it passed 31 to 34 of them. Park them in a separate low-confidence column.
- An address a verifier never returned a verdict for is UNMEASURED, not dead.
Never pay twice:
- Postgres table verified_emails(email, domain, verdict, source, verified_at).
- Before any paid step, look the domain up: a deliverable email verified within the last
60 days is reused at zero cost.
- Database emails go stale: anything last verified more than 60 days ago gets re-verified
(a ~90-day-old batch bounced 5.4% vs 4.4% for fresh ones).
- If the database is unreachable, carry on without it (fail open).
6. The wrong-business check (the step paid tools skip)
A verifier only proves the mailbox exists, not that it belongs to this business. Web pages
carry footer and privacy-policy addresses of web agencies, regulators and parent companies,
and they all verify fine. Keep an email only if:
- its domain is the business's domain or a subdomain of it, OR
- it's free-mail found as a mailto: on the business's own site, OR
- its domain shares a distinctive word (4+ letters, not inc/llc/group/services/properties)
with the business name or domain.
Drop the rest and log how many. In our test one commercial tool returned 19 "verified" emails that belonged to someone else, including a South African privacy regulator and a US state attorney general's office.
7. The full tool stack and what each costs
| Job | What we use | Cost |
|---|---|---|
| Brains, builds and runs it | Claude Code | your plan |
| Google Maps discovery | own scraper (part 2) + rotating residential proxy | ~$0.49 per GB of proxy traffic |
| Licence lists | state open data (part 3) | free |
| Website crawl | own crawler (part 4) | free |
| B2B database | QuickEnrich | subscription |
| Name + domain email finder | DiscoLike email-find | ~$0.003 per proven hit |
| Owner names from page text | any LLM | cents |
| Verifier | Reoon bulk | ~$0.0012 per address |
| Second verifier for catch-alls | BounceBan / OrbiSearch | ~$0.0075 / ~$0.0004 per address |
| Storage | Postgres | free tier is enough |
| Phone line type (only if you cold call) | Twilio Lookup line type intelligence | ~$0.008 per number |
What one real build cost: 3,123 local businesses, $29.40 in vendor spend, 2,369 verified emails. About 1.2 cents per verified email. Prices are from our runs; check current ones before you budget.
Extras worth adding: normalise phones to E.164, and pull facebook / instagram / linkedin / x / youtube / tiktok profile links from the homepage you already fetched (ignore share and plugin links).
Max Pidvalnyi, Maxionlabs. Questions or stuck? Reply to my DM on X, @marioleads.