Dedupe and normalize a contact list
Tidy a messy contact file: normalise every email, merge duplicate people, and flag role accounts, free-provider addresses and disposable domains. Most of the work is done locally for free; Ravn's validate_email supplies the flags that cannot be worked out from the text alone.
One command.
Installs into ~/.claude/skills for Claude Code. In Claude, upload the downloaded file under Settings → Capabilities → Skills.
npx ravn skill install dedupe-and-normalize-contacts
When to use
- Several lists were merged and the same person appears three times with different casing.
- The user wants a clean master file before importing into a CRM or a sending tool.
- "Dedupe this", "normalise these emails", "which of these are info@ or Gmail?"
Before you start
- Check the connection:
npx ravn whoami(free). - Costs: the local clean-up is free. Flagging with
validate_emailis 1 lookup per unique address after dedupe, so dedupe first and you use fewer lookups. - Ask which columns identify a person (usually email; sometimes name plus company when emails are missing) and which row should win when duplicates disagree (newest, most complete, or a source column).
Steps
- Normalise locally, writing to a new column
email_normalizedand leaving the original untouched: - Trim spaces, remove surrounding<>andmailto:, lowercase the whole address. - Remove obvious junk: blank cells, values without exactly one@, trailing punctuation. - Split cells that hold two addresses (a@x.com; b@x.com) into separate rows only if the user agrees. - Build a dedupe key from
email_normalized. For matching only (not for sending), you may also strip a+tagfrom the local part and, for gmail.com and googlemail.com, remove dots before the@. Tell the user if you do this. - Group rows by key. Merge each group into one row: keep the winning row's values and fill its empty cells from the others. Add
duplicate_countandmerged_from(row numbers). - For rows with no email, dedupe on lowercased name plus company domain, and mark them
match_type: name_companyso the user can review. - Save the deduped file, then flag it with Ravn. Dry run, sample, full:
npx ravn enrich deduped.csv --tool validate_email --column email_normalized --dry-run npx ravn enrich deduped.csv --tool validate_email --column email_normalized --limit 10 --out sample.csv npx ravn enrich deduped.csv --tool validate_email --column email_normalized --out clean.csv
- Over MCP, a deduped list of up to 1,000 can go in one
validate_emailscall, which already returns one verdict per unique address:
{ "emails": ["info@acme.com", "jane@gmail.com", "x7@mailinator.com"] }
- Read the flags:
rw_role_account,rw_free_provider,rw_disposable,rw_catch_all, plusrw_verdict. Add a singlecontact_flagcolumn:disposable,role,free,invalidor blank.
Output
clean.csv with every original column plus:
email_normalized,duplicate_count,merged_from,match_typerw_status(ok,not_found,error,dropped),rw_verdict,rw_risk_score,rw_reasonrw_role_account,rw_free_provider,rw_disposable,rw_catch_allcontact_flag
And a summary: rows in, rows out, duplicates merged, and counts of role, free, disposable and invalid.
Rules
- Never delete the user's original file. Write new files.
- Do not rewrite an address the person gave (for example removing Gmail dots) in the sending column. Those rules are for matching only.
- Keep opt-out and unsubscribe status when merging: if any duplicate opted out, the merged row is opted out. See /privacy#optout.
- Drop disposable, invalid and risky addresses from any send list.
- No FCRA uses. On a 429
fair_use_exhausted, stop and tell the human; on exit code 4, the 5-hour window is full and the run will wait unless--no-waitwas set.
Example prompt
I merged three exports into contacts-all.csv and there are loads of duplicates. Clean up the emails, merge the dupes, and tag which ones are generic inboxes, Gmail, or throwaway addresses.
More skills
Install them all with npx ravn skill install --all.