Most agencies think a lead list is a file. You buy it, or you scrape it, and now you "have leads." Then three weeks later you scrape the same city again, get half the same businesses back, and have no idea which ones you already emailed. The file was never the asset. The pipeline is the asset.
We run outbound off a scraped-lead engine, and the single biggest lever on its performance isn't the scraper or the email copy. It's curation — the boring middle step that turns a raw pull of 2,500 rows into a clean, deduplicated, tagged set of records that a CRM can actually use. Get curation right and every future pull compounds. Get it wrong and you're re-buying the same leads forever.
"The raw scrape is not the lead list. It's the ore. Curation is the smelting — and it's the step that determines whether your next pull adds value or just adds duplicates."
Here's exactly how the pipeline runs, and the rules we learned the hard way.
Rule one: import the whole pull, never cherry-pick
The instinct when a scrape comes back is to skim it and keep "the good ones." That's a mistake, and it's expensive. The moment you cherry-pick at import time, you've destroyed your ability to know what you already have. Next month you scrape the same category and can't tell the genuinely-new businesses from the ones you silently dropped last time.
So the rule is absolute: import every row of the pull. A recent decorative-concrete scrape came back with 2,529 businesses. All 2,529 went into a staging table — the ones with no website, the ones with no email, the borderline chains, all of them. Filtering is a read-time decision, not an import-time one. You filter when you build an outreach segment, not when you load the data.
Rule two: keep the no-email rows
This one feels wrong the first time. A big chunk of any scrape comes back with no email address — sometimes 40% or more. The temptation is to delete those rows because "you can't email them anyway."
Don't. A business with a name, address, phone, website, and Google reviews but no email is not a dead row — it's a lead you enrich later. Emails get appended from a second social-info pass, from the website crawl, from a manual look-up when the lead goes hot. If you deleted the row at import, that business is gone from your universe. We store the row with email = NULL and let the outreach layer filter on "has email" at send time. The record stays; it just doesn't get picked for an email blast until it has an address.
"A no-email row isn't garbage. It's a business you'll email in three weeks once you enrich it. Delete it now and you'll re-scrape it, re-pay for it, and never know you already had it."
Rule three: capture everything the scraper returned
Scrapers return far more than name and phone. A Google Maps pull includes street, city, ZIP, latitude/longitude, the place's CID (its permanent Google identifier), category, hours, review count, rating, and often several candidate emails. We learned — painfully — that an early version of our importer was silently dropping most of that. It kept name/phone/website and threw away the street, ZIP, geo coordinates, CID, and the extra email candidates.
That data is the difference between a flat list and an enrichable database. The CID especially matters: it's the one field that reliably identifies a business across pulls, because names and addresses drift but the CID is permanent. We now map the full raw payload at load time and keep the original JSON alongside the parsed columns, so nothing the scraper found is ever lost.
| Field | Why it's kept |
|---|---|
| CID | Permanent Google identifier — the only reliable key for dedup across pulls. |
| Lat / Lng | Enables distance-sorted outreach ("nearest 50 to this address") without re-geocoding. |
| Street / ZIP | Neighborhood and territory segmentation; mail-merge address lines. |
| Category | Segment targeting — but never a filter for down/SSL alerts (keep every category there). |
| email_1…email_4 | Candidate addresses. Keep all of them; pick the best at send time. |
| raw_json | The full original payload — so a future importer can backfill fields we didn't parse today. |
Rule four: dedup on identity, not on name
This is where most list-hygiene efforts fall apart. People dedup on business name, or on name + city, and it fails constantly because the same business shows up as "Joe's Concrete," "Joe's Concrete LLC," and "Joe's Concrete & Masonry" across different pulls. Name-matching is a losing game.
We dedup on the CID. Two rows with the same CID are the same business, full stop, regardless of how the name is spelled. When a new pull comes in, every row is checked against the existing set by CID: if it's new, it's inserted; if it already exists, the new data is merged into the existing record (a newer email, an updated review count) rather than creating a duplicate. That's how a pull can be 2,500 rows and only add 900 genuinely-new businesses — and you know it added exactly 900, because the other 1,600 matched on CID.
Rule five: one source tag per pull, mapped to a standing taxonomy
Every import gets exactly one source-list tag identifying which pull it came from — the city, the category, the date. That tag never changes and never gets reused. It's your audit trail: six months later you can answer "where did this lead come from and when did we get it?" instantly.
Separately, each lead is mapped into a standing taxonomy — a fixed set of categories your business actually uses, not the raw Google category string. Google returns dozens of near-identical category labels ("Concrete contractor," "Concrete construction company," "Cement contractor") that all mean the same thing to you. The importer maps those messy strings onto your clean internal categories at load time, so segmentation later is trivial.
"The source tag says where it came from. The taxonomy says what it is. Keep those two separate and you can slice the database any way you need six months from now."
Rule six: back up before every destructive step, and check the blocklist
Two guardrails wrap the whole pipeline. First, a blocklist: businesses that unsubscribed, bounced hard, or that we simply never contact get matched and excluded at import so they can never re-enter the outreach pool through a fresh scrape. A CID that's on the blocklist stays out no matter how many times it's re-scraped.
Second — and this is a rule we hold ourselves to on every project — you back up the current state before any bulk operation that deletes, strips, or overwrites. We learned this from an incident on another system where a bulk script overwrote 211 real email addresses that had been manually collected. Half were unrecoverable. Now nothing runs a destructive pass without dumping the prior values first. It costs thirty seconds and it has saved us more than once.
The payoff: the list becomes a living CRM
Run this pipeline for a few months and the "lead list" stops being a file you re-buy. It becomes a database that only grows, never duplicates, and gets richer with every pull. Every scrape adds net-new businesses and enriches existing ones. Every outreach send is logged against a permanent record. Every reply, every bounce, every unsubscribe updates the same row. You always know what you have, where it came from, and what you've done with it.
That's the difference between doing outbound off a spreadsheet and doing it off a system. The spreadsheet decays. The system compounds.
Where this fits in an engagement
Curation is quiet, unglamorous infrastructure — which is exactly why it's usually skipped and exactly why it's valuable. When we build an outbound engine for a client, this pipeline is the foundation we lay first, because everything downstream (the scoring, the personalized emails, the reply tracking) is only as good as the data underneath it. The deeper, client-specific version of this — the exact taxonomy, the enrichment sources, the scoring model tuned to a particular market — is something we build inside an engagement, on your data, for your market. The rules above are universal. The tuning is yours.

