Agent skill
list-enrichment
Enrich a list the user already has (a CSV, a spreadsheet export, a pasted table of names and companies, LinkedIn URLs or domains) with RouterGrowth: clean and dedupe it, fix missing or wrong company domains (company_search), find work emails across several data providers in turn (contact_find), verify every address (contact_verify), and add direct phone numbers (contact_phone) or full profiles (person_enrich) only when asked.
Filed under Prospecting and list building.
From RouterGrowth/skills · 17 skill entries · 2 · pushed 2026-10-28
What it does when it runs
Enrich a list the user already has (a CSV, a spreadsheet export, a pasted table of names and companies, LinkedIn URLs or domains) with RouterGrowth: clean and dedupe it, fix missing or wrong company domains (company_search), find work emails across several data providers in turn (contact_find), verify every address (contact_verify), and add direct phone numbers (contact_phone) or full profiles (person_enrich) only when asked. Reuses results the workspace already paid for and skips people who opted out. Use when the user says "enrich this list", "find emails for these people", "verify these emails", "get phone numbers for my leads", uploads a CSV of contacts, or wants a Clay-style enrichment run without a subscription.
Automated analysis of the skill and the 1 file 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
- None found.
- Hosts it reaches
- www.routergrowth.com
- Tool permissions it declares
- No
allowed-toolsin the frontmatter. It does act, so it runs under whatever permissions your session already grants. - Actions present in the files
- writes files
Ask about list-enrichment
Opens your assistant with this page's recorded context. Check the linked sources before relying on it.
Install it
View source on GitHub ↗git clone --depth 1 --filter=blob:none --sparse https://github.com/RouterGrowth/skills.git /tmp/skills git -C /tmp/skills sparse-checkout set "skills/list-enrichment" mkdir -p ~/.claude/skills/list-enrichment cp -R "/tmp/skills/skills/list-enrichment/." ~/.claude/skills/list-enrichment/
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 17 skills at once. Plugin skills are invoked as /<plugin>:<skill>, so they never collide with your own.
/plugin marketplace add RouterGrowth/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.
The skill
Source on GitHub ↗Reproduced in full from RouterGrowth/skills/blob/f745e770fa4001dd0f1a2bba0cd1a10e551fd2dd/skills/list-enrichment/SKILL.md, which is licensed MIT (skill frontmatter). 1,297 words, 13 headings.
List enrichment
The user brings the people; this skill brings back a verified work email for as many of them as the providers know, a phone where asked, and an honest count of what was found, what was risky and what was missed. Most of the money in a list job is spent here, so the order of the steps is the order that wastes the least.
Before you start
- The RouterGrowth tools must be in your tool list. If they are missing, the user connects RouterGrowth (Claude settings, Connectors: sign in, approve a workspace). Confirm with the free
get_workspace_balance. - Read the list the user gave (a file in the working folder, an attachment or a pasted table). Name the columns you found and map them: first name, last name, full name, company name, company domain, LinkedIn URL, existing email. Ask only when a column is ambiguous.
- Ask what the list is for once, and which fields they need: emails only (the default), emails and phones, or full profiles. Phones and profiles cost more per row; never add them by default.
- Call
get_operation_pricing(free) forcontact_findandcontact_verify, pluscompany_search,contact_phoneorperson_enrichif they will run. Readwaterfall.max_cost_for_allfor each: that is the cap you pass asmax_coston every lookup, because a lower cap skips the providers priced above it and turns findable rows into misses. Lookups are charged only when they find something, so the high cap does not raise the price of a hit. - Quote the whole job before the first paid call: rows to look up times the found price, as a range, since misses are free. Get a yes on anything likely above about $1. When no human is in the loop, the budget the caller set is the approval: stop at it and report.
- Call
list_tables(free). When tables are on, load the list into a table named after it (upsert_table_rows) and work from there; otherwise writeout/<list>-enriched.csv(Claude Code) or a markdown table in the chat.
Steps
1. Clean (free)
- Split full names into first and last name. Strip titles, emojis and suffixes ("PhD", "MBA") from names.
- Normalise domains: lowercase, no
https://, no path, and no subdomain:app.acme.comandwww.acme.combecomeacme.com(emails live on the root domain; a subdomain turns a findable row into a miss). A personal domain (gmail.com, outlook.com, yahoo.com, icloud.com) is not a company domain: clear it. - Dedupe on LinkedIn URL, then on first name + last name + domain. Say how many duplicates you removed.
- Rows that already carry an email skip step 3 and go straight to step 4.
2. Free checks before paying
get_subjects_historywith the LinkedIn URLs and emails you have (batches of up to 100): it returns what this workspace already found for each person. Reuse those results instead of paying again, and drop anyone the workspace already contacted if the user says so.- When tables are on,
list_suppressionsis the do-not-contact list: remove everyone on it now.
3. Fix the domains that are missing or wrong
contact_find needs the domain the person's email lives on. For rows with a company name and no domain, or a domain that missed in step 4 of an earlier run:
{"input": {"query": "Acme Robotics", "location": "Germany", "limit": 5}, "max_cost": "<cap>"}
Pass a location when the list has one: a bare name matches homonyms worldwide. Take domain only from a row whose name matches the company and whose country fits; the database also matches words in company descriptions, so a short common name ("Sides", "Clay") returns unrelated companies, and then the domain stays empty and you say so. When two rows fit, keep the one whose linkedin_url matches the person's company page or whose description fits what the person does. Country variants and second domains are a common cause of misses (acme.de for a German team at acme.com, a short brand domain beside the main one): when a find misses on one, try the other once. The company's description or website often names it.
4. Find the emails
One call per row without an email:
{"input": {"first_name": "Alex", "last_name": "Rivera", "company_domain": "acme.com"}, "max_cost": "<max_cost_for_all>"}
Each call tries the data providers in turn, cheapest first, until one finds the address; you pay only for the one that found it. Results carry email, status and employment_verified. A no_match that says the person opted out with RouterGrowth ends it for that person: no retry, no other tool, remove the row. Keep a running total from each receipt and stop at the approved budget.
5. Verify every address
contact_verify with {"email": ...} on every address, including the ones the user brought and the ones step 4 found:
valid: keep.invalid: drop, and keep the row with an empty email so the user sees the miss.catch_allorrisky: keep in a separate column and say so. The domain accepts anything, so the address is unproven: fine for a small personal send, wrong for a bulk send.unknown: free; leave unverified and say so.
6. Phones and profiles (only when asked)
contact_phonewith{"email": ...}or{"profile_url": ...}per row,max_costat itsmax_cost_for_all. Charged only when a number is found, at the price of the provider that found it, which can be several times an email: quote the high end. Results carryphone,type(mobile or landline) andconfidence. Flag a number whose country code matches neither the person's location nor the company's country ascheck. Never dial or text from this skill.person_enrichwith{"profile_url": ...}for the current role, role history, education and skills, when the user needs more than an email.
Gate
Before handing the list over, show: rows in, duplicates removed, already known (reused), suppressed, emails found, valid, catch-all or risky, invalid, not found, phones found if asked, the total spent (the sum of this job's charges from list_runs, not a wallet difference: the balance is shared by every session on the workspace), and ten sample rows. Then write the file or the table.
Rules
max_coston every paid call; on lookups, thewaterfall.max_cost_for_allfromget_operation_pricing. Never cap a lookup at the cheapest provider's price.- Never guess an email pattern (
first.last@) and present it as found. A guessed address is not in the output unless the user asks for it, and then it carries the labelguessed, unverified. - Collect only the fields the job needs. Every enriched row carries the run ids that produced it.
- Emails, phones and profiles come from RouterGrowth's third-party data providers. If anyone asks where a value came from, point to https://www.routergrowth.com/privacy and https://www.routergrowth.com/privacy/opt-out.
- This skill sends nothing. To write to the list, hand the verified rows to
email-list-pipeline(email after approval) orlinkedin-drafts(drafts). - If a tool fails in a way its error does not explain, offer to report it and call
submit_feedbackonly if the user says yes.
What a real run looked like
Our own pilot list: 582 founders and GTM leads at UK and French B2B SaaS companies. The first provider found 440 emails (76%); a second provider found 59 of the 81 misses it was given; of the addresses found, 92% verified valid and 6.5% came back catch-all or risky. Misses that stop at the first provider are the expensive mistake, which is why step 4 passes max_cost_for_all.
Output
The user's list with these columns added: domain_used, email, email_source_run, verify_status, phone, phone_type, notes. Plus the totals above, and the misses at the bottom with the reason (no domain, not found, invalid, opted out, suppressed), so the user can see what was filtered and why.
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.
- list-enrichment by extruct-ai · 111
- list-enrichment by ogikicollins · 0
- blitz-list-builder by growthenginenowoslawski · 797
- google-maps-list-builder by growthenginenowoslawski · 797
- list-builder by growthenginenowoslawski · 797
- list-expander by growthenginenowoslawski · 797
- list-quality-scorecard by growthenginenowoslawski · 797
- perfect-company-list by growthenginenowoslawski · 797
Need help setting it up?
This page tells you what list-enrichment 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.