DEV Community

Gerald Mitchell
Gerald Mitchell

Posted on

How to Clean an Email List Locally with a Free Open-Source Node CLI

Disclosure: we built and maintain the tool in this post (email-list-cleaner, MIT). This article was written with AI assistance and reviewed by us.

Bounces hurt sender reputation, and many "list verification" services require uploading your contacts to a third party. If you just need to catch the obvious problems, you can do it on your own machine with a zero-dependency Node.js CLI.

Repo: https://github.com/5012763083a-oss/email-list-cleaner

What it checks

  • Syntax: is the address well-formed?
  • MX lookup: does the domain publish mail servers? Public DNS only, with no SMTP probing, so no mail server is contacted.
  • Disposable domains: flags throwaway inbox providers.
  • Role accounts: flags info@, admin@, support@ and similar addresses.
  • Typos: suggests fixes like gmial.com → gmail.com.
  • Duplicates: removed case-insensitively.

Nothing in your list leaves your machine. The only network traffic is DNS lookups for the domains.

Usage

Run it straight from GitHub with npx (Node required):

npx github:5012763083a-oss/email-list-cleaner contacts.csv -o cleaned.csv
Enter fullscreen mode Exit fullscreen mode

Options:

--column email   # which CSV column holds the address
--no-mx          # skip DNS lookups (fully offline)
--no-dedupe      # keep duplicates
Enter fullscreen mode Exit fullscreen mode

For example, if your export has the address in a Email Address column and you're offline:

npx github:5012763083a-oss/email-list-cleaner export.csv --column "Email Address" --no-mx -o cleaned.csv
Enter fullscreen mode Exit fullscreen mode

Reading the output

Your original columns are kept, and these are appended:

normalized,status,reason,syntaxValid,domain,hasMx,mxHosts,isDisposable,isRoleAccount,isFreeProvider,didYouMean
Enter fullscreen mode Exit fullscreen mode

status is one of valid, invalid, risky or unknown. A practical workflow:

  1. Drop invalid rows (bad syntax or no MX).
  2. Review risky rows: disposable or role addresses may be fine for a B2B list but not for a newsletter.
  3. Check didYouMean: fixing [email protected] recovers a real subscriber instead of losing them.
  4. unknown usually means a DNS lookup failed; rerun later. It's also the status you get for every address with --no-mx, with the reason mx_not_checked.

Open cleaned.csv in a spreadsheet and filter by status.

Limits

MX checks tell you a domain can receive mail, not that a specific mailbox exists. That's deliberate: confirming mailboxes requires SMTP probing, which is slow and can get your IP flagged. For most list hygiene, syntax + MX + disposable + typo checks catch the bulk of bad addresses.

The free edition processes the first 500 rows per file. It's MIT-licensed, so you can fork it and change FREE_LIMIT in bin/email-clean.js.

Run the tests with npm test.


If you'd rather support the project, Email List Cleaner Pro ($9) removes the cap and adds batch mode and a summary report: https://6907000850732.gumroad.com/l/khnxbj?ref=devto

Top comments (2)

Collapse
 
sujalkhatri profile image
Sujal Khatri •

One dedupe nuance that bites email lists in practice: case-insensitive dedupe is the right default, but the duplicates that survive it are usually alias variants, not case variants. [email protected], [email protected] and [email protected] are three different strings but one mailbox, and no string-based dedupe catches them. If a list is mostly Gmail addresses, a normalisation pass before dedupe — strip dots from the local part, drop +tags — usually finds another chunk of "duplicates" that exact matching missed.

Related trick I use with suppression lists: when you need the addresses that are on both of two lists (rather than a merged list), concatenate the two files and run sort combined.txt | uniq -d — the output is exactly the overlap.

Collapse
 
damiana_teliti_5b0178f9f4 profile image
Damiana Teliti •

Nice that the limits section is honest about MX vs mailbox. One practical reason to stay local-only for this layer: most home and office connections block outbound port 25, so SMTP probing from a laptop usually just times out anyway, and you'd misread that as "unknown" for half the list.

A small idea for the output: the didYouMean column is probably the highest-value thing here. Typo fixes recover real people, while everything else only removes them. Might be worth sorting those rows to the top of cleaned.csv so they actually get reviewed.