Companies House Gave One Director Eight Companies. We Nearly Emailed Him Eight Times
One email address appeared eight times in our prospect list, almost spamming the same director – here’s how we fixed duplicate outreach at the person level.
Introduction
At Pharoah we build tools that help engineering teams move fast without breaking trust. Our own outbound pipeline is a living test of those principles: we send carefully targeted messages to prospects who might benefit from SOLAI, GhostWire or our labs programmes. In August 2026 a routine quality check revealed a pattern that could have turned a well‑intentioned campaign into a source of spam complaints.
The Problem Spotted
Our prospect‑generation script pulls director information from UK Companies House via the public API. Each director record is linked to a company number, and we treat each company‑director pair as a separate prospect entry because, in theory, a director could be acting in different capacities across distinct businesses.
During a daily review we noticed that one mailbox – let’s call it director@example.com – appeared eight times among the twenty slots we were about to process. That meant the same human would have received eight identical pitches if we had sent the batch unchanged.
The root cause was simple: Companies House lists the same director under multiple active company numbers. In our data set that director was associated with eight different companies, all of which were valid, active entities. Our pipeline, however, deduplicated only on the composite key company_number + director_name. Because the company_number differed, the system saw eight unique records and queued eight separate emails.
Why Traditional Dedup Fails
Many outbound tools stop at deduplicating by email address or by a hash of the contact record. That works when each email address maps to a single human identity, but it breaks when:
- The source data treats a person as multiple records (as with Companies House director‑company links).
- Alias addresses or forwarding rules cause the same mailbox to appear under different local parts (e.g., first.last@domain.co.uk vs flast@domain.co.uk).
- CRM imports introduce slight variations in name spelling or title.
In our case the first bullet was the culprit. The email address was identical across all eight records, but because we never collapsed on that field alone we were poised to spam the same inbox.
Building the Fix: Three‑Layer Dedup
We decided to enforce deduplication at the point where the recipient experiences the message – the inbox – rather than at the source‑data granularity. The solution consists of three independent checks:
1. Batch‑Build Normalisation
When the prospect list is assembled, we normalise every email address:
- Lowercase the entire string.
- Remove dots and plus‑tags where the mail provider ignores them (e.g., Gmail).
- Strip whitespace.
We then create a hash of the normalised address and use it as a provisional dedup key. Any duplicate hashes are collapsed into a single prospect entry, retaining the richest set of associated metadata (company numbers, director names, SIC codes).
2. AI Review Gate
Before a batch moves to the AI‑driven copy‑personalisation stage, we run a lightweight script that re‑checks the normalised‑email hash against a rolling Bloom filter of addresses already seen in the current outreach window (the last 48 hours). If a match is found, the entry is flagged for manual review rather than automatically discarded – this preserves edge cases where a director genuinely intends to receive separate messages for distinct roles.
3. Send‑Time Final Guard
Even with the previous layers, we add a final safeguard at the SMTP handoff. Our sending service maintains an in‑memory set of normalised addresses that have already been dispatched in the current batch. Immediately before queuing a message, we test the recipient’s address against this set; if present, the message is dropped and a debug log entry is written.
This three‑tiered approach guarantees that, regardless of how many company records a director holds in Companies House, the same inbox receives at most one piece of outreach from us per campaign window.
Lessons Learned
The incident reinforced a core principle we now apply across all data‑ingestion pipelines:
Your dedup key must match the unit the recipient experiences, not the unit your data source emits.
In practical terms:
- Identify the true consumer of your output (here, a human reading an email).
- Derive a canonical identifier from the attributes that consumer actually sees (email address, phone number, etc.).
- Apply dedup at every stage where data could diverge – ingestion, transformation, enrichment, and delivery.
We also updated our internal documentation to include a checklist for new data sources:
- List all fields that could uniquely identify a person or organisation.
- Determine which of those fields are stable across source variations.
- Implement normalisation rules that reflect real‑world equivalence (case‑insensitivity, provider‑specific alias handling).
- Add unit tests that deliberately feed duplicate‑by‑person, different‑by‑source records and assert a single output.
Closing
Engineering is as much about preventing harm as it is about enabling feature velocity. By tightening our deduplication logic we protected a director’s inbox, kept our sender reputation intact, and reinforced a habit of thinking from the recipient’s perspective.
If you’re interested in how we approach reliable outreach, data pipelines, or the specific products we’re building – SOLAI (live from £49/month), GhostWire (early access), Secure OS (labs programme) and Fusion (by engagement) – please visit our products page: https://pharoahtechnology.co.uk/products/. You can also reach out directly with questions or feedback; we’re happy to discuss the trade‑offs we made and hear how you handle similar challenges in your own stacks.