Skip to content

Automation  ·  Revenue operations

How to build lead deduplication and merge

Two reps call the same person in the same week because a webinar list arrived after a demo request. The shape of deduplication that catches it, the prompts that build it, and the production detail that decides whether it holds.

Built with Tray Headless

  1. System Salesforce leads
  2. Step Scored detection
  3. Step Survivorship
  4. Step Move activity
  5. System Salesforce master
Also Review queue Owner conflict

Survivorship is decided field by field, and an owner conflict leaves the workflow rather than being resolved by it.

The short answer

What is lead deduplication?

Deduplication is four parts: detection on a scored rule set instead of an exact email match, field-level survivorship so the merged record keeps the best value from each side, activity and relationship history preserved from both, and an owner rule that never silently reassigns. The mistake that costs most is survivorship. A merge that keeps whichever record is older throws away the enrichment, the corrections and sometimes the phone number that the newer one had.

What matters here

  • Exact email match finds the easy duplicates. The expensive ones differ by a plus-address, a nickname or a job change.
  • Survivorship is per field, not per record. Keep the newest phone, the human-entered title and the richest firmographics, whichever side they came from.
  • Never lose activity. Tasks, emails, campaign membership and opportunities all move to the survivor, or the merge destroys the reason to merge.
  • An owner change is a business decision, not a merge side effect. Flag it instead of doing it quietly.
  • Merges must be reversible for a window. The first month of any dedupe rollout produces merges somebody will want back.

Who this is for

You run revenue operations or data operations. The CRM has duplicates arriving faster than anybody can clear them, and the last attempt at a bulk merge is still remembered. What you want is detection you can tune and merges you can defend.

How it works in practice

Everything that sits between two records existing and one surviving.

  1. 1

    Detection runs on a scored rule set

    Exact email, normalised email, name plus company, name plus phone. Each rule carries a confidence, not a verdict.

  2. 2

    High confidence merges automatically, the rest queue

    The middle band goes to a person. Auto-merging a 0.8 match is how you lose a record somebody needed.

  3. 3

    Survivorship is decided field by field

    Newest phone, human-entered title, richest firmographics, earliest created date. Not whichever record is older.

  4. 4

    All activity moves to the survivor

    Tasks, emails, campaign memberships, opportunities and attachments. Nothing is left pointing at a deleted id.

  5. 5

    Owner changes are flagged, never silent

    If the survivor has a different owner, that is surfaced for a decision rather than applied quietly.

  6. 6

    The merge is reversible for thirty days

    Both original records are retained, so a bad merge is an undo instead of a reconstruction.

What deduplication is made of

Dedupe is not a matching query. It is four parts, and the second one is where trust is won or lost.

Scored detection

Several rules, each with a confidence: exact email, normalised email, name plus company, name plus phone. Duplicates rarely announce themselves with an identical address.

Field-level survivorship

A rule per field instead of a winning record. The best phone and the best title are frequently on different sides.

History preservation

Activity, campaign membership, opportunities and attachments all move. A merge that loses the history has removed the reason anybody wanted the merge.

An explicit owner rule

Ownership is commercial. The merge surfaces the conflict; a human resolves it.

The Tray Headless prompts

Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.

Once per project, run /tray-workflows:set-workspace to pick the workspace these build in. Point it at a sandbox first.

  1. 1

    Set up, then learn the real object model

    Merging without knowing what points at a record is how history disappears.

    Headless skills build-workflow

    Use build-workflow. The systems in play are Salesforce, or whatever we
    run in those seats.
    
    I need Lead and Contact fields with types, plus every object that carries
    a lookup to them: Task, Event, CampaignMember, Opportunity, Attachment,
    and any custom object. Merging without that list is how activity history
    disappears.
  2. 2

    Detect on several rules, each with a score

    Exact email finds the easy ones. The costly duplicates are not exact.

    Headless skills build-workflow tray-patterns

    Use build-workflow to build detection. Run these rules, each producing a
    confidence:
    
      Exact match on lower(email)                          1.00
      Normalised email, plus-addressing and dots stripped  0.95
      Same name and same company, normalised               0.85
      Same name and same phone in E.164                    0.85
      Same company and same phone, different name          0.60
    
    Take the highest score per candidate pair. Above 0.90 merges
    automatically, 0.70 to 0.90 goes to a review queue, below that is not a
    duplicate.
    
    Run it on insert and as a nightly sweep, because duplicates arrive in
    list imports rather than one at a time.
  3. 3

    Decide survivorship field by field

    The part that decides whether people trust the merge.

    Do not pick a winning record. Decide per field:
    
      Email          the one on the record with more activity
      Phone          the most recently modified non-empty value
      Title          human-entered beats enriched, newest beats oldest
      Company        the enriched value if both are present
      Firmographics  the most complete set, not the newest
      Created date   always the earliest, so tenure is not reset
      Source         the earliest, so first-touch attribution survives
    
    Show me the resolved record before writing when it goes through the
    review queue, with each field labelled by which side it came from.

    Keeping the earliest created date and source is what stops deduplication quietly destroying first-touch attribution reporting.

  4. 4

    Move every piece of history

    A merge that loses activity has removed the reason to merge.

    Headless skills tray-gotchas

    Use tray-gotchas, then reparent everything to the survivor:
    
      Tasks and Events, with their original timestamps intact
      Campaign memberships, deduplicated so one person is not counted twice
         in the same campaign
      Opportunities, and stop the merge entirely if both records have open
         opportunities, because that is a conversation not an automation
      Attachments and notes
      Any custom object with a lookup
    
    Verify nothing points at the losing record before it is retired, and keep
    both originals for thirty days so a bad merge is an undo.
  5. 5

    Never reassign an owner quietly

    Ownership is commercial, and silent changes destroy trust in the whole system.

    If the two records have different owners, do not resolve it in code.
    
    Keep the survivor owner, flag the conflict, and notify both owners with
    the two records and the activity on each. A human decides.
    
    Report weekly: duplicates detected by rule, auto-merge rate, review queue
    size and age, owner conflicts raised, and merges reversed. The reversal
    count is the honest measure of whether the thresholds are right.
  6. 6

    Validate, then hand it over

    So thresholds move on evidence, not by ticket.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production. Start in report-only mode for a fortnight: detect and
    queue, merge nothing, and read the queue.
    
    Then open the same workflow in Tray Build so revenue operations can adjust
    the thresholds and the survivorship rules in the visual canvas.

What it connects to

Dedupe reads and rewrites the system of record, so it touches everything attached to a person.

Salesforce

Read leads, contacts and everything with a lookup to them. Merge, reparent activity and retire the loser.

Reads and writes

Marketo

Keep the person record aligned, so a merged lead does not remain two people in the marketing database.

Reads and writes

Slack

Raise owner conflicts to the two reps involved, and post the review queue to whoever owns data quality.

Writes

Snowflake

Land every merge with the survivorship decisions, which is what makes a reversal reconstructable.

Writes

Outreach

Suppress sequences for a retired record, so a merged person does not keep receiving email from a dead id.

Writes

Same build, other stacks

The design does not change if you run something else in one of these seats. The same prompts build it against Microsoft Dynamics 365, Google BigQuery, Microsoft Teams, HubSpot, Databricks or Google Chat.

Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.

Running it in production

Dedupe destroys records by design. That needs more care than most integrations.

The platform runs it, not a laptop under a desk

Nightly sweeps and insert-time detection execute on the same engine, with a full history of every merge performed.

Every merge is reversible for thirty days

Both originals retained with the survivorship decisions recorded. A bad merge is an undo, not a reconstruction.

Managed credentials, not secrets in a config file

Merge permissions delete records by design. That credential belongs in your workspace with a named owner, not in code alongside the logic that uses it.

Ops owns the thresholds

Detection scores and survivorship rules open in Tray Build, tuned by the team watching the review queue.

Start in report-only mode

Detect and queue for a fortnight before merging anything. The first read of the queue always changes the thresholds.

Questions people ask

Why not only match on email?

Because exact email finds the easy duplicates. The expensive ones differ by a plus-address, a nickname, a personal address on a second signup, or a job change at the same company.

What is survivorship?

Deciding which value wins per field, not which record wins. The newest phone and the human-corrected title are frequently on different records, and picking a winning record throws one of them away.

What happens to activity history?

It all moves: tasks, events, campaign membership, opportunities and attachments. If both records have open opportunities the merge stops, because that is a conversation between two reps rather than an automation.

Should a merge change the record owner?

No. Keep the survivor owner, flag the conflict and let a person decide. A silent reassignment is the fastest way to lose the sales team's trust in the CRM.

How do I roll this out safely?

Report-only for a fortnight: detect and queue, merge nothing. Read the queue, move the thresholds, then enable auto-merge above the high band with thirty day reversibility.

Further reading

Background on the same subject, for the case rather than the build.

Last reviewed September 2026.