Revenue-based pricing  ·  Our fee is half the savings we project

Ellipse Automation
All posts
databuild-logcold-email

We Matched 6,658 Contacts by Domain. A Third Were Wrong.

·Etienne Chanut

We needed owner or manager contacts for ~123,000 cleaned Italian hotel domains, cross-referenced against our own 150-million-contact B2B database. The obvious approach: find the hotel's domain in our indexed company table, then pull every contact linked to that company_id. That was the first build, and it barely worked.

Matching contacts to companies by email domain turned out to be the better move: scanning every contact's business email domain directly against the hotel list, instead of routing through a company record, found 3.2x more matches. It also let through a false positive good enough to catch on sight, a US "President of Walgreens Deutschland" sitting on top of a 12-room family pension in Campania. Fixing that collision took a structural filter, not a smarter query.

The short version

  • The company-ID join returned 1,263 domains, mostly noise; scanning email domains directly found 4,071, 3.2x more.
  • The combined file of 6,658 contacts hid a domain collision: a US "President of Walgreens Deutschland" matched to a 12-room pension in Campania.
  • Location data was 95% blank, so the fix split by domain TLD (.it vs. everything else) instead.
  • Final split: 2,976 contacts trusted in the .it core, 3,682 quarantined for manual review.

Why the join matters more than the database size

Database size means nothing if the contact-to-company join is missing, and for small businesses it usually is. Getting to the database took its own fix first: it sat on a Tailscale tagged node that couldn't inherit my user-scoped share, so I opened a reverse SSH tunnel before matching could run.

Most enrichment pipelines assume every contact row carries a clean company_id pointing back to an indexed company record, an assumption that holds for large companies and falls apart for a 12-room family hotel in Campania. That blind spot isn't specific to hotels: any company-indexed database misses small businesses the same way, since those records were built around companies large enough to index. The workaround has to live at the contact level, not the company level.

How the matching pipeline runs

01

Company-ID join

1,263 domains, 224,345 raw contacts, mostly noise

02

Direct domain scan

4,071 domains, 3.2x more matches than the join

03

Combine and dedupe

6,658 contacts across 3,873 hotels

04

TLD split

2,976 trusted .it contacts, 3,682 quarantined for review

Company linkage undercounted the hotels badly

Routing through company_id returned 1,263 domains and 224,345 raw contacts. After cleanup that fell to 1,158 domains and 3,046 contacts, because most of the 224,345 rows were noise loosely attached to the company object, not real owner or manager contacts at a hotel.

Matching contacts to companies by email domain tripled the hit rate

Scanning contacts' email domains directly against the hotel list found 4,071 matching domains. That's a 3.31% hit rate against the precise probe count of 122,867 domains. That's 3.2x what company linkage found, from the same underlying database.

Getting there took noise filtering, stripping corporate domains that had leaked into the list and hotel-chain domains that would have attached the wrong property; after cleanup the direct-scan approach held at 3,878 domains and 6,750 contacts.

Company linkageDirect domain scan
Domains matched (raw)1,2634,071
Domains after cleanup1,1583,878
Contacts after cleanup3,0466,750
Multiple vs. company linkagebaseline3.2x

Combining and deduplicating both passes produced a final file of 6,658 contacts across 3,873 hotels. If 3,046 plus 6,750 collapsing to 6,658 looks strange, that's dedup at work: the two passes overlap heavily, and a contact counts once no matter how many paths found it.

The false positive: a US president running a 12-room pension

A manual preview of the combined file surfaced a US "President of Walgreens Deutschland" matched to a 12-room family pension in Campania. That's the failure class: domain collision, where an unrelated company happens to share a domain string with a hotel in the target list. Nothing in the pipeline flagged it, because on paper the domain matched cleanly.

The obvious fix was to filter by the contact's declared location. It failed outright: over 95% of location fields in the matched set were blank, so filtering on it would have thrown out almost the entire file, good matches included.

TLD split: the signal that actually held

The reliable signal turned out to be the domain ending, not location. A US contact essentially never holds a work email on an @hotelname.it domain, so splitting the file by TLD separated the trustworthy matches from the collision risk without needing that field at all.

That split carved the combined file into a trustworthy .it core, 2,976 contacts across 2,032 hotels, and a collision-risk .com/other tier, 3,682 contacts across 1,841 hotels, quarantined for review rather than deleted. Quarantine beats delete here: the risky tier still holds real matches, it just needs a human look before it goes anywhere.

6,658
Contacts matched by domain
3,873
Hotels matched
2,976
Trusted in the .it core
3,682
Quarantined for review
Why the TLD split works this well

The split works this well specifically because of how the underlying database skews. The 150 million figure from the top of this post counts individual contacts; 20.5 million is a different layer underneath them, the distinct company domains those contacts sit under, and it's US-centric: only 39,454 of those 20.5 million company domains are .it. That imbalance is why a non-.it domain colliding with an Italian hotel name is far more likely than an .it domain doing the same, and why the TLD line was worth drawing where it was.

The noise filtering behind the 4,071-to-3,878 domain cleanup ran on the same logic in miniature: corporate domains that had leaked into the hotel list, like cisco.com and ferrari.com, got stripped, along with hotel-chain domains that would have attached the wrong specific property to a contact.

What the TLD split doesn't tell you

The TLD split is a heuristic tuned to this specific geography, and it says nothing about whether the contact holds the right role at the hotel. Move the problem to a country where the database isn't so lopsided toward US domains and the collision math changes, so the split needs re-tuning, not reuse.

No AI sat anywhere in this fix. Staring at a preview file caught what the pipeline could not: a person looked at 6,658 rows, noticed a US pharmacy executive couldn't plausibly run a 12-room pension in Campania, and that observation is what changed the pipeline, not a smarter model or a better prompt.

The takeaway

If you're matching contacts to companies by email domain at any real scale, budget time to preview the output by hand before you trust the counts. The join that looks cleanest on a dashboard, a domain match, a company_id, a location filter, can still be wrong in a way no query catches. When one field is unreliable, look for a structural signal that isn't, the way TLD stood in for a location field that had already proven worthless here.

This week: pull a sample of your own matched or enriched contacts and check how many rows have blank data on whatever field you'd normally filter by. If that number is high, you have the same blind spot we had, and the fix is a structural signal from the data you already collected, not a new data source.

We've written more on what happens once a list like this is usable for outreach, in building lead lists that convert and in what a million cold emails taught us about what happens after the match is made. More builds like this one are in case studies.

Key takeaways

  • Run both matching paths on any small-business list: the company-ID join alone will silently skip most of your targets.
  • Always preview the merged file by hand before it ships; a title that can't plausibly belong to the matched company is your collision alarm.
  • When the obvious filter field is unreliable (95% of our location data was blank), look for a structural signal instead, like the domain's TLD.
  • Quarantining uncertain matches beats deleting them: the flagged tier still held thousands of real contacts, it just needed a manual pass.
  • No AI caught the false positive here. A human looking at a preview did, which is still the cheapest QA step in any matching pipeline.

Common questions

What's the best way to match contacts to companies by email domain?

Scan every contact's business email domain directly against your target list instead of joining through an indexed company record. Against ~123,000 Italian hotel domains, direct scanning found 3.2x more matches than company-ID linkage.

Why does matching contacts by email domain create false positives?

Domain collisions: an unrelated company happens to share a domain string with the business you're targeting. In our case a US 'President of Walgreens Deutschland' matched to a 12-room family pension in Campania because both sat on the same domain pattern in a 150-million-contact database.

How do you catch bad matches when the contact data has no location field?

Look for a structural signal instead of a data field. When over 95% of our matched contacts had blank location data, we split the file by domain TLD instead - a US contact essentially never holds a work email on a country-specific domain like .it.

Ready to see the math

Your bottom line has room. We can show you where.

Book a free 30-minute call. We'll look at your refund rate and growth trajectory, then show you the savings we'd project. No pitch deck, no commitment.

Run your numbers with us

Free 30-minute call. The math is yours to keep either way.