IDInternals Decoded
AI System Design
Deep DivesAdvanced11 min readMay 2026

Entity Resolution: One Thing, Many Names

Sam K., Samuel Kim, sam@acme.com. Deciding what's the same thing is harder and more important than it looks.

Part 13 of 14AI System DesignView series →

After Part 12’s deep dive into tracing agent failures, you might think the hardest problem is fixing a broken reasoning loop. It’s not. The hardest problem is giving the agent a clean view of who and what it’s reasoning about. That’s entity resolution. In Mailmind, it’s the layer that decides whether “Sam K.”, “Samuel Kim”, and sam.kim@work.com are one person or three. Get it wrong and every downstream answer that depends on “Sam” silently fragments.

Here’s the hook. Merge two different real people into a single canonical node and your assistant will confuse Alex Chen the recruiter with Alex Chen the investor for weeks. Every thread with one Alex leaks into the other. The agent’s accuracy drops with no obvious error message. A false merge poisons retrieval, memory, and every summary that crosses the merged node. Avoiding that means building a pipeline that is paranoid about sameness.

Why is deciding sameness so hard?

Think of a hospital with three record systems each spelling a patient’s name a different way. Without resolution, a doctor sees a third of the history. Mailmind faces the same mess. Senders appear as “John D.”, “John Doe”, “john@company.com”, and “j.doe@personal.net” across threads. A simple string match fails on nicknames, typos, and abbreviations. Add cross-language forms and you get “Giovanni Rossi” vs “John Rossi”.

The real kicker is ambiguity. Two real people genuinely named Sam Kim might both email you. The system must keep them separate even if their names overlap heavily. At the same time, related-but-distinct identities, like a person’s work self and personal self, might need to be linked for an “everything about Sam” query but kept distinct for a “only work Sam” filter. That tension between merging too much and too little is the core challenge.

How does the resolution pipeline work?

Mailmind processes every new contact through a six-stage pipeline that avoids the naive O(n²) trap. Start with a million known contacts. Comparing every new record against all of them would require a million comparisons per email, utterly infeasible. Instead, the system groups records into cheap, lossy buckets and only compares within those buckets.

The stages go like this. First, blocking splits the world into coarse groups. Second, candidate generation picks plausible matches inside each block. Third, feature extraction computes similarity signals across multiple dimensions. Fourth, scoring combines those signals into a single confidence score. Fifth, decision thresholds sort matched pairs into auto-merge, human review, or keep-separate bins. Sixth, when a merge is confirmed, the system fuses all information into one canonical node and preserves an unalterable audit trail.

How does blocking avoid comparing every pair?

Blocking uses cheap, high-recall rules to drastically shrink the comparison space. For email contacts, Mailmind’s blocks include the sender domain, the first two letters of the first name plus the domain, or a phone area code if present. A record with domain @acme.com only gets compared against other records in the same domain block. A record with no shared block is never considered.

Impact of Blocking
Without Blocking
  • Compare against all 1M contacts
  • ~1 billion operations per email
  • O(n^2) complexity
With Blocking
  • Only compare within relevant blocks
  • ~1000 candidates per email
  • Under 200ms per email
Without blocking, each new email would trigger a billion comparisons. Blocking reduces this to a manageable set.

The price of blocking is that real matches can be missed if the block key is too strict. For example, if someone changes employers, the domain block no longer captures them. Mailmind compensates by using multiple overlapping blocks and by running a slower, periodic full-block sweep that reconsiders records with no matches. The result is a practical balance: millions of cheap comparisons and very few that require deep similarity scoring.

How are features extracted?

Once candidates are generated, the system extracts numeric signals from raw attributes. Name similarity uses several measures at once: Levenshtein edit distance on normalized strings, Jaccard similarity over token sets, and phonetic encodings like Soundex that catch “Katherine” vs “Catherine”. Attribute overlap checks for identical phone numbers, mailing addresses, or company names. Relationship overlap measures how many shared contacts appear in both threads. Context similarity looks at signature patterns, writing style, or recurring meeting rooms.

Each feature vector becomes a row of numbers. No single feature is strong enough to trigger a merge. The system treats them as mutually reinforcing clues. A 90% name match plus an identical phone number is strong evidence. A 90% name match with no other overlapping signals is suspicious.

How are scores combined?

Mailmind uses a lightweight gradient-boosted classifier trained on historical edge cases. The model takes a feature vector for a candidate pair and outputs a probability that they are the same entity. Rule-weighted scoring served the first version, but the classifier adapts to the specific noise patterns of this inbox. LLM (large language model) judgment is also an option for tricky pairs. Sending the two profiles to a small language model with a few-shot prompt yields a flexible verdict, but costs 100x more per comparison. So it’s reserved for pairs that land in the review band.

The scoring layer is not one-size-fits-all. Different entity types get different models. A person merge classifier weighs relationship overlap more heavily. A company merge classifier relies on address and industry hard blockers. This separation keeps the model from learning a single brittle threshold that works for nothing.

How does the decision threshold work?

Scores from the classifier land in one of three bands. Above 0.95, the system auto-merges. Between 0.65 and 0.95, the pair goes to a human review queue, surfaced inside Mailmind’s admin panel as “possible duplicate: Sam Kim.” Below 0.65, the pair stays separate. The boundaries were set by measuring precision and recall on a golden set of known duplicate pairs and deliberately keeping the auto-merge band narrow.

Human reviewers see a side-by-side diff of the two records: aliases, email addresses, recent threads, and any hard blocker warnings. They accept, reject, or link the identities. Every human decision feeds back into the training set so the classifier improves over time. False merges are still possible, but the middle band catches most of them before they corrupt the graph.

How does Mailmind store a resolved person?

Once a merge is confirmed, the system creates one canonical node with a stable ID. The original records become aliases pointing to the same node, never deleted. This design means a query for “Sam K.” always routes to the canonical node. The canonical node carries a primary display name, a list of aliases, a verified phone number and email when available, and references to any linked identity nodes.

Linking handles the work-self and personal-self problem. Mailmind creates a separate “identity” sub-node for each different email domain and links both to the same person. A query that scopes to work threads follows the work identity link. A query that wants everything about Sam follows the person node. This linked structure avoids two bad outcomes: merging identities that should stay separate and duplicating a person because they use two email addresses.

PropertyValue
Canonical person IDUUID v7
Primary display nameDerived from most frequent verified name
AliasesSet of all observed name strings
Hard identifiersVerified phone, verified email
Linked identitiesWork identity, personal identity, etc.
Audit logTimestamped merge and unmerge events
Review statusauto_merged, human_reviewed, unmerged

Why are hard blockers more important than similarity?

Hard blockers are rules that forbid a merge no matter how high the similarity score. Two “Acme Industries” in different countries and unrelated industries are almost certainly different companies. Mailmind never auto-merges across a hard blocker like different verified phone numbers, different company domains, or zero shared contacts across months of threads. These rules are coarse but they prevent the most catastrophic failures.

Similarity is seductive. A 0.98 name match feels like a slam dunk. But without a shared unique identifier, it can still be a trap. The system enforces the rule “no shared unique identifier means no auto-merge.” If two Sam Kim records share no verified phone or verified email and have never appeared in the same thread, the pair goes straight to human review, no matter how similar their names look. That one rule catches the two-different-Alex-Chen problem before it poisons every downstream answer.

How does incremental resolution keep up?

A full rebuild of the entity graph for every new email would waste compute and introduce lag. Mailmind runs incremental resolution. When a new contact arrives, the system computes its blocks, fetches only the candidates in those blocks, scores the pairs, and decides. Unmatched external records that don’t link to any known entity are staged in a pending pool and revisited later when more signals accumulate.

This incremental path runs in under 200ms per email. Every night, a batch job reprocesses the pending pool, re-scores previously reviewed pairs that have gained new signals (like a new shared contact), and runs a full pass on low-activity blocks that might contain missed matches. The combination of fast inline resolution and overnight re-evaluation keeps the assistant current without slowing down the inbox.

Key Resolution Numbers
200ms
Incremental resolution per email
0.95
Auto-merge threshold
0.65
Human review lower bound
Performance and threshold metrics from Mailmind's pipeline.

Frequently Asked Questions

Q: Can’t an LLM just decide if two contacts are the same?

An LLM can, and it handles messy natural language nuance better than a classifier. Mailmind reserves that path for pairs in the human review band where the cost is acceptable. Using it for every pair would blow the latency and cost budgets. A focused classifier on pre-extracted features gives you 98% of the value at 1% of the cost.

Q: What happens when a user manually splits a merged person?

The system records an unmerge event. It creates two new canonical nodes, reassigns edges by original source record (threads, calendar events, etc.), and flags all previously generated outputs that crossed the merged node for regeneration. The audit log preserves the full history so the team can diagnose what went wrong.

Q: How do you handle a person who changes email domains?

If the new email has no other matching signals, it creates a separate identity node that sits in the pending pool. Over time, if that identity starts sharing threads, contacts, or meeting patterns with an existing person, the nightly batch job promotes it to a linked identity under the same canonical node. Hard blockers like a different verified phone number prevent linking if the change is suspicious.

Q: Does blocking miss matches across different blocks?

Yes. That’s a deliberate tradeoff. The nightly batch sweep periodically compares records that ended up in disjoint blocks but share fuzzy name and relationship signals. False negatives from blocking are corrected within 24 hours. Real-time inline resolution accepts a small false negative rate to keep latency low.

Q: How do you measure resolution quality?

A golden set of manually curated duplicate and non-duplicate pairs, built from historical decisions, acts as a holdout. Precision, recall, and F1 on that set are monitored daily. Drift detection fires if the classifier’s performance shifts, usually when inbox demographics change. Shadow mode runs the current and candidate models side by side on live traffic without affecting merges, flagging disagreements for review.

Test yourself

A Mailmind user reports that for three weeks, all summaries about “Alex Chen” mixed threads from two different people. Investigation reveals a single false merge fused two Alex Chens six weeks ago. One Alex is a recruiter at a staffing firm. The other is a venture investor. They share no threads, no phone numbers, and no company domain. The merge happened because both signed emails with “Alex” and their full names matched exactly. How would you fix the immediate mess and prevent it from happening again?

Answer: First, unmerge the node into two canonical entities. Every edge, email threads, calendar invites, receipts, must be reassigned to the correct Alex based on the original sender address or other source metadata. Any agent outputs generated while the merge was active are flagged for regeneration. Second, audit the classifier’s feature weights. The false merge reveals overreliance on name string similarity without enough weight on relationship overlap and hard identifiers. Add a hard blocker rule: if two records share zero threads, no verified phone, and no overlapping organization, no auto-merge is allowed regardless of name match score. Raise the weight of relationship overlap in the scoring function and tighten the auto-merge threshold. Finally, add the false pair to the golden evaluation set so the retrained model has a direct example of the failure mode. The combination of hard blockers, retraining, and a growing golden set makes this class of failure rarer every week.

If you want this kind of breakdown every week, how real systems actually work under the hood, subscribe to Internals Decoded at internalsdecoded.com. The next time your assistant confidently mixes up two people, you’ll have the blueprint to fix it. The final episode closes the series by tackling how to keep these pipelines healthy after they ship, as users, data, and the world change out from under them.

#entity-resolution#deduplication#canonical-records
More from the library
The Newsletter

Keep up with AI. One email a week.

One thoughtful email each week. Unsubscribe whenever you like.