Systems Lab

Agent skill

email-list-cleaner

Cleans an old or inherited email list before anyone sends to it: repairs syntax, dedupes, applies suppression, flags role and free-provider addresses, validates every unique address with ScraperCity's Email Validator, and splits the result into send-ready, catch-all, hold and removed files with a reason on every dropped row.

activeNeeds a keyActs undeclared2,756 words

Filed under CRM and RevOps.

From scrapercity/gtm-skills · 31 skill entries · 0 · pushed 2026-11-02

What it does when it runs

Cleans an old or inherited email list before anyone sends to it: repairs syntax, dedupes, applies suppression, flags role and free-provider addresses, validates every unique address with ScraperCity's Email Validator, and splits the result into send-ready, catch-all, hold and removed files with a reason on every dropped row. Use when the user says "clean this list", "verify these emails", "this list is 2 years old", "my bounce rate spiked", "is this list safe to send", or hands over a CSV export before a campaign or re-launch.

Automated analysis of the skill and the 11 files bundled beside it. A skill’s own description is written to be selected by an agent, so it describes the job and not the dependencies.

Keys and connectors you must supply
  • SCRAPERCITY_API_KEY
Hosts it reaches
  • alexberman.com
  • app.scrapercity.com
  • blog.google
  • datatracker.ietf.org
  • emt.gartnerweb.com
  • glockapps.com
  • help.instantly.ai
  • help.lemlist.com
  • helpcenter.smartlead.ai
  • hunter.io
  • knowledge.apollo.io
  • knowledge.workspace.google.com
  • leanb2bbook.com
  • learn.microsoft.com
  • m3aawg.org
  • publicsuffix.org
  • scrapercity.com
  • senders.yahooinc.com
  • sendersupport.olc.protection.outlook.com
  • substrate.office.com
  • support.google.com
  • support.microsoft.com
  • techcommunity.microsoft.com
  • u.yourdomain.com
  • uribl.com
  • www.apple.com
  • www.bls.gov
  • www.bmj.com
  • www.crexi.com
  • www.linkedin.com
  • www.m3aawg.org
  • www.rfc-editor.org
  • www.spamhaus.org
  • www.surbl.org
  • www.theregister.com
  • www.twilio.com
  • www.unicode.org
  • www.zerobounce.net
  • zerobounce.net
Tool permissions it declares
No allowed-tools in the frontmatter. It does act, so it runs under whatever permissions your session already grants.
Actions present in the files
shellwrites files

Ask about email-list-cleaner

Opens your assistant with this page's recorded context. Check the linked sources before relying on it.

Is this safe to install?ClaudeChatGPT
Adapt it to my stackClaudeChatGPT
What else do I need for it to workClaudeChatGPT
Rather ask a human? Talk to Cheetah
git clone --depth 1 --filter=blob:none --sparse https://github.com/scrapercity/gtm-skills.git /tmp/gtm-skills
git -C /tmp/gtm-skills sparse-checkout set "skills/list-building/email-list-cleaner"
mkdir -p ~/.claude/skills/email-list-cleaner
cp -R "/tmp/gtm-skills/skills/list-building/email-list-cleaner/." ~/.claude/skills/email-list-cleaner/

Picked up without a restart. A project skill of the same name is shadowed by your personal one. For one repository only, swap ~/.claude/skills for .claude/skills. Claude Code docs ↗

Or take the whole library

This repo ships a .claude-plugin manifest, so Claude Code can install all 31 skills at once. Plugin skills are invoked as /<plugin>:<skill>, so they never collide with your own.

/plugin marketplace add scrapercity/gtm-skills
/plugin

The folder is the same in every client that implements the format — 46 of them — so if yours is not above, only the destination changes.

Before you install: this skill will not complete its job on a bare agent. It needs SCRAPERCITY_API_KEY, which you have to obtain separately.

Reproduced in full from scrapercity/gtm-skills/blob/46d446143690c8048b17817aecf36990ea6880e5/skills/list-building/email-list-cleaner/SKILL.md, which is licensed MIT (skill frontmatter). 2,756 words, 22 headings.

Email List Cleaner

Overview

This skill turns a list of unknown quality into five files and a report: send_ready.csv (valid addresses only), catch_all.csv, hold_unknown.csv, role_review.csv and removed.csv, plus the suppression rows to add to the sending platform. Every input row ends up in exactly one file, and every removed row carries a reason code.

The mistake it prevents: validating the raw export as-is and sending to everything marked "valid" or "catch-all" in one campaign. Paying to validate duplicates and already-suppressed rows wastes money, and mixing catch-all addresses into the main send is how a list that "passed validation" still bounces at 6% and trips the sequencer's auto-pause. The order of operations here (repair, dedupe, suppress, then validate) is what keeps the bill and the bounce rate low.

When to Use

  • "Clean this list before we send." "Verify these 20,000 emails."
  • A list more than four weeks past its last validation, or one that has never been validated.
  • A bounce spike on a live campaign (pause first, then clean the remaining rows).
  • An inherited list: a former employee's export, an event list, an acquired company's CRM.
  • A re-launch to a list that was last mailed months ago.

Use a different skill when:

The user wantsUse
New contacts, not a cleaned old listicp-list-builder or cold-email-list
Missing emails or mobiles filled into a CRM exportcrm-enricher
Domain, DNS or warmup problems behind the bouncesemail-deliverability
Help choosing or configuring a sequencersending-platforms

Prerequisites

InputWhyDefault if unknown
The file (CSV or XLSX) and which column holds the emailEverything starts hereAsk; detect columns containing @
Where the list came from and when it was last mailedSets risk tier and catch-all policy"Unknown source, last mailed a year or more ago" (tier C)
Intended use (cold email, newsletter, CRM import, ads audience)Role and catch-all rules differ by useCold email
List type (named B2B contacts, local businesses, ecommerce stores)Role address policyNamed B2B contacts
Suppression exports (unsubscribes, bounces, complaints, customers)Must be applied before validationNone supplied; say so in the report
Previous bounce rate on this list, if mailedCalibrates the bounce projectionUnknown

Authoritative Foundations

SourcePrinciple it contributes
Google Email sender guidelines (Feb 2024)Keep spam rate under 0.3%, target under 0.1%. Bounces and complaints from a dirty list feed both.
Yahoo Sender best practicesRemove invalid recipients promptly and track bounces and inactive recipients.
Microsoft Outlook high-volume sender requirements (5 May 2025)Lists list hygiene and bounce monitoring as expected practice for senders over 5,000 a day.
M3AAWG Sender Best Common PracticesSuppress an address after it bounces consecutively at least two times over two weeks or more; not mailing a list for a year or more is a sign of a dirty list.
IETF RFC 2142 (May 1997)Defines role mailboxes (info, sales, support, abuse, noc, security, postmaster, webmaster). Basis for the role flag.
Hunter, Email verification guide (4 Nov 2025)Verify shortly before the campaign. In Hunter's test, over 2% of a list went invalid within four weeks and 5.5% within eight. Basis for the four-week re-validation window.
Alex Berman (alexberman.com, cold email templates)Verify every email before sending and keep bounces under 2%. The stop line for every send plan here.
Instantly, High bounce auto-pausePauses a campaign at 5% bounces by default (adjustable) once it has sent 200. Sets the size of the first read on a test batch.
references/list-quality-standard.mdNormalization, dedupe keys, validation buckets.
references/deliverability-primer.mdWhat each validator status means for sending.

Source details and a routing table for this skill's files: references/framework-notes.md.

Workflow

1. Profile the file

Count rows, find the email column (or columns: some exports carry email, email_2, work_email), and record blank emails, cells with more than one address, and the source and last-mailed date. Assign a risk tier:

TierSignalEffect
AValidated within four weeks (Hunter), last send bounced under 2% (Berman)Re-validate only rows older than four weeks
BMailed within the last year, not validated in the last four weeksFull clean, catch-all rows in their own campaign
CNot mailed for a year or more (M3AAWG's dirty-list marker), or source or last-mailed date unknownFull clean, catch-all rows held out of cold campaigns, first send is a test batch of a size the user sets

2. Repair syntax

Apply the rules in references/syntax-repair-and-role-rules.md: trim, strip mailto: and display names (Dana Ruiz <[email protected]>), split multi-address cells into separate rows, remove zero-width characters, fix known consumer-domain typos (gmial.com to gmail.com). Never rewrite a corporate domain. Log every change in fix_applied. Rows that still fail syntax go to removed.csv with reason syntax; they are not sent to the validator.

3. Dedupe

Build email_key: lowercase the full address; for consumer gmail.com and googlemail.com addresses only, also drop dots and any +tag in the local part. Keep one row per key using the survivorship order in references/list-quality-standard.md section 4b. Record how many rows each key collapsed.

4. Suppress before you pay

Match the deduped rows against unsubscribes, hard bounces, complaints, current customers, open opportunities and competitor domains, in that order (exact email first, then domain). Suppressed rows go to removed.csv with the list name as the reason. RFC 2142 network-operations mailboxes (abuse@, postmaster@, hostmaster@, noc@, security@) are removed here too: RFC 2142 routes them to the people who handle abuse reports, not buyers.

5. Flag role and free-provider addresses

Set is_role = true for the local parts listed in the reference file. On a named-contact B2B list, role rows are validated but routed to role_review.csv; on local business and ecommerce lists they stay in the main flow. Set is_free = true for consumer providers; on a B2B list keep them only when the person is a sole proprietor or the business uses that address publicly.

6. Show the count and cost, then validate

Unique addresses to validate x $0.00169 is the cost. Call check_wallet, show the funnel so far and the cost, and wait for a go-ahead before submitting. Then submit:

  • A list short enough to fit in one tool argument: MCP validate_emails with the array.
  • Larger files: one-column text files, submitted through the CLI or REST, one run per file; split into chunks of a size the user picks only so a failed run is cheap to retry. The runbook is references/batching-and-revalidation-runbook.md.

Poll check_run_status; a typical run finishes in minutes. download_results when SUCCEEDED.

7. Map results and route every row

Read the downloaded header row first; the documented columns are email, email_result, email_quality, subresult, free, plus sc_ enrichment columns such as sc_is_catch_all, sc_mx_provider and sc_mx_security_gateway. Route with references/status-handling-matrix.md:

email_resultFileSuppress?
valid (not role on a B2B list)send_ready.csv, ordered high, medium, low email_qualityNo
valid and role on a B2B listrole_review.csvNo
valid_catch_allcatch_all.csv, ordered by evidence score (quality, address format, person confirmed, no gateway)Only if it later bounces
unknownhold_unknown.csv, re-validate once on a later date (greylisting servers defer first attempts, RFC 6647)No
invalidremoved.csv, reason invalidYes, add to the platform blocklist
disposableremoved.csv, reason disposableYes

8. Check the projection and hand off

Hard bounces on send_ready.csv should stay under 2% (Berman; Instantly's default auto-pause is 5% once a campaign has sent 200). Report the invalid rate per source: a source far above the others is stale or mis-mapped and should not be reused. When the list lives in a CRM, write email_status and email_validated_at back to every record and enroll sequences only from a view filtered on valid status validated within four weeks, so the next campaign cannot pull an unchecked row. Write the report from templates/output-template.md, run scripts/check-output.py on it, and set the re-validation dates: four weeks after validation for unsent rows, the retry date for unknowns, and at once for unsent rows at any domain that hard-bounces during the send.

ScraperCity Implementation

StepToolPrice (catalog)
Balance and plancheck_walletFree
Validatevalidate_emails (POST /api/v1/scrape/email-validator, body {"emails": [...]})$0.00169 per email
Poll, fetchcheck_run_status, download_resultsFree
Replace an invalid address for a high-value contactfind_emails with contacts: [{first_name, last_name, domain}]$0.05 per email found, free if not found

ScraperCity's validator dedupes the submitted array and bills unique addresses. Dedupe on your side anyway: the email_key rule in step 3 catches Gmail dot and case variants an exact match misses, and the count you quote the user should equal the count on the bill. The start response returns emailCount and an estimatedCost; compare emailCount with your unique count before letting a large run continue, and cancel_run if they disagree.

MCP call for a small list:

{ "emails": ["[email protected]", "[email protected]", "[email protected]"] }

CLI for a large file (the file must hold one address per line; the CLI keeps any line containing @, so a multi-column CSV would send whole lines):

npx scrapercity email-validate --file chunk_01.txt
npx scrapercity poll <runId>
npx scrapercity download <runId> -o chunk_01_results.csv

Worked cost estimate

A 12,400-row event and webinar list, last mailed 14 months ago (tier C):

StageRows
Raw rows12,400
After splitting 31 two-address cells12,431
After removing blank emails (212) and unrepairable syntax (61)12,158
After dedupe (1,388 duplicates)10,770
After suppression (402 matches) and network-role removal (17)10,351
ItemUnitsUnit priceCost
Validation10,351$0.00169$17.49
Optional replacements for 40 invalid VIP contacts, 20 found20 found$0.05$1.00
Total$18.49

Validating before suppression would have billed at least 480 more addresses (402 suppressed, 17 network-role, 61 broken), $0.81, plus up to 1,388 near-duplicates that the validator's own dedupe may not merge (case variants, Gmail dots and +tags), up to $2.35 more. Show this table and wait for a yes before submitting.

Error handling

ResponseAction
402check_wallet, report the shortfall, stop.
409The same batch was submitted moments ago. Find the earlier run with list_runs and use it.
429Rate limited: back off, then retry once.
Run FAILEDResubmit that chunk once; if it fails again, split it in half and record both run ids.

Output Format

  1. send_ready.csv: valid only, columns per references/list-quality-standard.md section 2 plus email_status, email_status_raw, email_quality, email_validated_at, is_role, is_free, fix_applied.
  2. catch_all.csv, hold_unknown.csv, role_review.csv: same columns.
  3. removed.csv: every dropped row with removed_reason (no_email, syntax, duplicate, unsubscribed, bounced, complained, customer, competitor, network_role, invalid, disposable).
  4. suppression_additions.csv: invalid and disposable addresses formatted for the sequencer's blocklist import.
  5. The report from templates/output-template.md: list profile, cleaning funnel, validation results, segment files, suppression additions, cost, re-validation schedule, next steps.

Small jobs (a handful of addresses, "are these 12 safe to send"): skip the files and the report. Return one table with each address, its email_result, email_quality and the action (send, separate catch-all campaign, retry on a date, remove), then one line with the cost.

  • What the reader receives is the deliverable only, in product names (Lead Database, Email Validator, Email Finder, Mobile Finder, Website Finder): no tool ids, JSON tool calls, run IDs, skill names, file paths, reference section numbers or runbook IDs; a downstream step is written out as the checks or commands the team runs, never handed to another skill by name; no tables of checks that were not run (one "Not checked:" line instead); and no repeated identical rows where one template plus a list will do. scripts/check-output.py fails a report that carries a tool id, skill name or path.

Quality Check

  • Every input row appears in exactly one output file; file row counts sum to the raw row count plus rows created by splitting multi-address cells.
  • No row reached the validator before dedupe and suppression.
  • Validation buckets sum to the number validated, and the billed count equals the unique count.
  • send_ready.csv is 100% valid, validated within four weeks, with 0 duplicates and 0 suppression matches.
  • No abuse@, postmaster@, noc@, security@ or hostmaster@ address in any send file.
  • Catch-all rows sit in their own file, ordered by evidence, with the user's daily volume and the 2% bounce stop written in the report.
  • check_wallet ran and the user approved the cost before the run.
  • scripts/check-output.py report.md prints PASS.

Common Pitfalls

MistakeWhy it failsFix
Validating the raw exportPays for duplicates and suppressed rowsRepair, dedupe, suppress, then validate.
Passing a multi-column CSV to the CLI --file flagThe CLI sends every line containing @ as an addressExport a one-column text file per chunk.
Treating valid_catch_all as validThe server accepts everything at validation time and bounces laterSeparate file, separate campaign at the user's daily volume, stopped past 2% bounces.
Auto-fixing corporate domainsacme.co may be the company's own domainFix only the known consumer-domain typos and log each fix.
Deleting removed rowsYou lose the reason and re-import them next quarterKeep removed.csv and push invalids to the blocklist.
Re-validating unknown rows in a loopEach pass bills againOne retry on a later date, then route by the catch-all rule or drop.
Sending every valid row on day one from a tier C listStale mailboxes surface only on sendFirst send a test batch; continue only if bounces stay under 2% after 200 sends.
Sending catch-all rows in file orderThe weakest rows can use up the first 200 sends and stop the catch-all campaignOrder by quality, address format, recent confirmation and gateway; send the best first.
Sending more catch-alls at a domain after one hard-bouncedThat server rejects at delivery what it accepted at validationPause the domain's unsent catch-all rows; re-validate its unsent valid rows.
Validation status kept only in the CSVThe next campaign pulls the same contacts from the CRM uncheckedWrite status and date back; enroll only from the four-week valid view.

Execution Artifacts

FileOpen it when
references/framework-notes.mdYou need the source behind a threshold or the routing table for this skill.
references/status-handling-matrix.mdMapping validator columns to files, combining result, quality and flags.
references/syntax-repair-and-role-rules.mdRepairing addresses, the typo map, role and free-provider lists.
references/batching-and-revalidation-runbook.mdFiles over 1,000 rows, chunking, run tracking, failures, re-validation cadence.
references/list-quality-standard.mdColumns, dedupe keys, buckets (shared).
references/deliverability-primer.mdMailbox provider sender rules and bounce monitoring (shared).
references/scrapercity-catalog.mdTool ids, parameters and prices (shared).
references/scrapercity-platform.mdPlans and billing rules (shared).
templates/output-template.mdWriting the report.
examples/worked-example.mdA finished report for a 12,400-row event list.
scripts/check-output.pyFinal gate: python3 scripts/check-output.py report.md.

Related Skills

  • cold-email-list to rebuild a list when this one comes back mostly invalid.
  • crm-enricher to find replacement emails and write results back to the CRM.
  • icp-list-builder for new contacts that match the same profile.
  • email-deliverability when bounces persist on a cleaned list.
  • sending-platforms for blocklist import and auto-pause settings.
  • scrapercity-cli for every CLI command and flag.

Files bundled with it

These load only when the skill asks for them, so they cost nothing until it runs.

Other skills for the same job

Different authors, same problem. Matched on the words in the skill name, across every library in the catalogue except this one.

Need help setting it up?

This page tells you what email-list-cleaner does and what it needs. Cheetah builds the agent setup it runs inside: data, CRM, sequencing and the guardrails.

Book a call →

The directory stays free. There is nothing gated behind this.

Build my GTM engine

Tell us what you want to improve. Fedor reviews the workflow and replies by email when there is a useful fit.

Fedor reviews every request personally. If there is a useful fit, he will reply by email.