Engineering8 min read

The one CRM feature clients never ask for and always need: duplicate detection

Across every lead-generation CRM I have built, the highest-value feature was never on the original requirements list. It was telling an agent that the customer they just typed in already exists.

Muhammad Qasim, author

Muhammad Qasim

Full Stack Developer · Rawalpindi, Pakistan

About the author

Short answer

Warn, do not block. Match on normalised phone and email first, fall back to fuzzy name plus date of birth, and always show the agent which existing record you matched and who owns it.

01Why duplicates are expensive, not just untidy

In a healthcare lead-generation CRM I built for a US client, duplicate records were not a data-hygiene annoyance. They were a revenue and compliance problem. Two agents working the same customer meant paying twice for one lead, calling a person who had already enrolled, and occasionally selling a second policy that had to be unwound.

The team had a manual process for this: before creating a lead, check whether the customer exists. With two hundred agents under call-time pressure, that check happened when it was convenient. The fix was not training. It was making the system do the check.

02Matching strategy, in priority order

Duplicate detection is a precision-versus-recall problem, and the two failure modes have very different costs. A missed duplicate costs money. A false positive costs agent trust — and once agents learn to dismiss the warning reflexively, the feature is dead.

So I match in tiers, strongest signal first, and I tell the agent which tier fired.

  • Normalised phone number. Strip formatting, country codes and leading zeros to a canonical form before comparing. This is the single highest-precision signal in practice.
  • Normalised email. Lowercase, trim, and for common providers strip dot and plus-suffix variants.
  • Government or policy identifier, where the domain has one. Exact match only.
  • Fuzzy name plus date of birth. Names arrive misspelled constantly, so this needs trigram similarity rather than equality — and on its own it is a warning, never a certainty.
  • Address similarity as a tie-breaker, useful for households but never sufficient alone.

Normalise before you compare. The overwhelming majority of "duplicates" a naive system misses are the same phone number written four different ways.

03Warn, do not block

My first instinct was to prevent creating a duplicate. That was wrong, and the reason is instructive: legitimate duplicates exist. Two family members share a phone number. A person genuinely re-enrols after a lapse. A previous record was created in error and the agent is correcting it.

A hard block turns those cases into support tickets and teaches agents to work around the system with fake data — which is far worse for data quality than the duplicates you were trying to prevent.

So the interface warns. It shows the matching record, which agent owns it, its current status, and when it was last touched. Then it offers three actions: open the existing record, create anyway with a reason, or cancel. The reason field matters — it turns overrides into an auditable trail instead of a silent bypass.

04Where to run the check

On the server, always. Enforcing it in the interface only means the API is unprotected, and in a CRM the API is exactly what bulk imports and integrations use — precisely the paths that generate duplicates fastest.

For responsiveness I run it twice: a debounced lookup as the agent types the phone number, so the warning appears before they have filled the rest of the form, and a definitive check on submit. The first is a convenience, the second is the guarantee.

On the database side this needs real indexes. Normalised phone and email get functional indexes on the normalised expression, not the raw column, or the index is never used. Fuzzy name matching needs a trigram index. Without both, duplicate checking becomes the slowest query in the system precisely as the table grows.

  • Store the normalised form as a generated column and index that, rather than normalising at query time.
  • Use a trigram index for fuzzy name comparison.
  • Scope the check to the tenant. Cross-tenant duplicate warnings leak data between customers.
  • Rate-limit the as-you-type endpoint. It is a customer-lookup API and should be treated as one.

05What it was worth

On that CRM the duplicate alert became the feature managers cited most in reviews, despite never appearing in the original brief. It removed a manual cross-check from every single lead creation, cut double-selling, and gave managers a record of when overrides happened and why.

If you are scoping a CRM, put this in the first release. It is cheap to build early and disproportionately expensive to retrofit once you have a large table of records nobody trusts.

TopicsCRMData qualityDjangoPostgreSQLLead management

Found this useful?

I write these from live project work. Follow along or get in touch if you want this kind of thinking applied to your product.

Quick answers

Should a CRM block duplicate records or just warn?+

Warn, and require a reason for the override. Legitimate duplicates exist — shared household phone numbers, genuine re-enrolments, corrections to bad records. Hard blocks push agents into entering fake data to get past the validation, which damages data quality more than the duplicates would have.

What fields should duplicate detection match on?+

Normalised phone number and email give the highest precision. A domain-specific identifier such as a policy number is exact when available. Fuzzy name plus date of birth catches misspellings but should only ever raise a warning, and address similarity works as a tie-breaker rather than a primary signal.

Does duplicate checking slow the CRM down?+

Only if it is indexed wrongly. Normalisation must happen at write time into an indexed generated column rather than at query time, and fuzzy name matching needs a trigram index. Done properly the check stays fast well past a million records.

Keep reading

All articles

Projects behind this article

The production systems where these decisions were made.

All case studies
  • Healthcare Lead Generation CRM — SaaS project preview
    SaaSCRM

    Healthcare Lead Generation CRM

    A production CRM and lead tracking system for a US healthcare client, built with React, Django, Python, AI d

    Read case studyLive
  • Expensive CRM — CRM project preview
    CRMRevenue management

    Expensive CRM

    A company operations CRM for managing revenue, audits, salaries, expenses, reporting and financial visibilit

    Read case study
  • Beta Auto CRM — CRM project preview
    CRMAutomotive operations

    Beta Auto CRM

    A testing-stage CRM with lead generation, salary workflows, attendance management, chat and company manageme

    Read case studyLive
  • FE CRM — CRM project preview
    CRMLead tracking

    FE CRM

    A multi-tenant CRM for lead generation, sheet uploads, centre-wise tracking, monthly filters, policies, char

    Read case study

Related services

If this describes a problem you have right now, these are the ways I can help.

All services
  • CRM Development

    Off-the-shelf CRMs force your team to work the way the software wants. I build CRM systems around the workflow you a

    Explore service
  • Django & Backend

    The backend is where a product either scales or quietly rots. I design data models that match the business, APIs tha

    Explore service
  • API & Integrations

    Integrations fail quietly. I build them with retries, logging and clear failure states, so when a third-party servic

    Explore service
  • SaaS Development

    Taking a SaaS idea to paying customers means solving multi-tenancy, billing, permissions and analytics before you wr

    Explore service