Blog/Sales & enrichment
5 min read

Email List Enrichment: Triage, Dedupe by Domain, Then Pay

Enrich a list of email addresses with company and person data: sort the list for free, look up each domain once, and call the person endpoint last.

Email List Enrichment: Triage, Dedupe by Domain, Then Pay

Turn a column of email addresses into company and person data without paying for the same company twice. Paste this into your agent:

Set up https://monid.ai/SKILL.md, and then use Monid to take my list of email addresses, drop the free-mail and role addresses, look up each company domain once, and give me a table with address, company, industry, employee range and country.

One prompt enriches a whole list. 1 min setup · 3-6 hrs saved.

You have a spreadsheet of email addresses from a form, an event or a CRM export, and you want company and person fields next to each one. Most guides start with the provider. This one starts with the list: how to sort it for free, how to look up every company domain once instead of once per address, and when a person lookup is worth running at all. We ran the calls on 2026-10-16, so every field and bill below is what came back. It runs through Monid, the OpenRouter for agent tools: one key, one balance, every tool priced per call.

What is the easiest way to enrich a list of email addresses?

Monid. Install it once, add a key, and your agent calls the enrichment endpoints directly, with no vendor account, no plan and no upload step.

  • Pay per call. One company or one person lookup is billed at $0.0049 (Verified 2026-10-16 on the people enrichment tools page). Buy calls as you need them: no monthly plan and no minimum top-up. Discovery and inspection are free; only a run bills.
  • One install, every tool. The same key and balance also reach the email verifiers and the people databases you may add later, so a second provider is one more monid run, not a second account.
  • Lives inside your agent. Install the CLI, add your key, paste the prompt above, and the endpoints are there whenever your agent needs them. Your agent reads SKILL.md and learns the rest, so setup is about a minute.

For a list of 1,000 addresses on 250 different company domains, a lookup per domain is 250 calls, about $1.225 (Verified 2026-10-16). A lookup per address is up to 1,000 calls, about $4.9. The rest of this guide is how to get to the smaller number.

How do you enrich an email list without paying twice for a company?

Sort the list, collapse it to its unique domains, look up each domain once, and only then run the person lookup on the addresses that need it. The reason is that a list of work emails is mostly repeats: ten colleagues share one company, and the company record is identical for all ten. On 2026-10-16 the company block inside a person lookup and the stand-alone company lookup for the same domain came back with the same company id and the same fields, so the second address at a domain pays for data you already hold.

RouteWhat you getCost shapeFits
Upload the CSV to a database platformEnriched rows in a web app and an exportPlans, often quoted by the seatA sales team working in one tool
Write your own lookup against public pagesWhatever your searches findYour hoursA one-off research task
Run every address through a person endpointPerson plus company, one call per addressPer callSmall lists, or when you need titles
Monid triage, then domain, then personCompany for every domain, person only where it paysPay per call, buy flexiblyLists, CRM clean-ups, agents

For agents

Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:

set up https://monid.ai/SKILL.md

It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.

For humans

npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main

Sort the list for free

Put one address per line in emails.txt. This script splits free-mail, role and personal addresses and writes the unique domains. It uses no calls, so it costs nothing to run twice.

import collections

ROLES = {"info", "support", "sales", "hello", "admin", "contact", "billing", "careers", "jobs", "press", "media", "noreply", "no-reply", "team", "office"}
WEBMAIL = {"gmail.com", "yahoo.com", "outlook.com", "hotmail.com", "icloud.com", "proton.me", "aol.com"}

people, roles, webmail = [], [], []
for line in open("emails.txt"):
    email = line.strip().lower()
    if "@" not in email:
        continue
    local, domain = email.rsplit("@", 1)
    if domain in WEBMAIL:
        webmail.append(email)
    elif local in ROLES:
        roles.append(email)
    else:
        people.append(email)

by_domain = collections.defaultdict(list)
for email in people + roles:
    by_domain[email.rsplit("@", 1)[1]].append(email)

with open("domains.txt", "w") as f:
    f.write("\n".join(by_domain))
with open("people.txt", "w") as f:
    f.write("\n".join(people))

print(len(people), "personal", len(roles), "role", len(webmail), "webmail", len(by_domain), "domains")

Edit the two sets to fit your list. Role addresses stay in the domain count on purpose: the address says nothing about a person, but the company behind it is still worth one lookup.

Look up each domain once

monid inspect -p hunterio -e /companies/find
monid run -p hunterio -e /companies/find --query '{"domain": "stripe.com"}'

Then loop over domains.txt:

mkdir -p companies
while read -r d; do
  monid run -p hunterio -e /companies/find --query "{\"domain\": \"$d\"}" > "companies/$d.json"
done < domains.txt

Each domain bills one call at $0.0049 (Verified 2026-10-16) when the lookup returns a record. Join the files back to your rows on the part after the @.

Run the person lookup last

while read -r e; do
  monid run -p hunterio -e /combined/find --query "{\"email\": \"$e\"}" > "people/$e.json"
done < people.txt

Create the people folder first with mkdir -p people. Run this only on the personal addresses, and only if you need a name, a title or a seniority label that the company record cannot give you.

Or hand the whole job to an agent

Give this to your agent

$Set up https://monid.ai/SKILL.md, and then use Monid to take my emails.txt, drop free-mail and role addresses from the person step, look up each unique company domain once, then look up the personal addresses, and give me one table with address, company, industry, employee range, country, job title and seniority, leaving a cell blank when nothing came back.

Can you enrich an email list yourself with Python?

Yes, and the sorting half is easy, as the script above shows. The hard half is the data. A person or company record comes from a large collected index, and your options for building one are scraping public pages address by address, or buying a dataset. Scraping one address at a time means search queries, parsing, retries and a repair whenever a page changes, and the match rate depends on pages that mention the person.

What Python is good at is the glue: parsing the CSV, grouping by domain, joining results back and handling the empty cases. What it is poor at is being the index. The Monid route is the choice for anything you will run more than once. The lookup and its upkeep sit behind one call, you read typed JSON instead of HTML, and a domain is $0.0049 (Verified 2026-10-16). If you want to see what one provider offers by domain before you build, Hunter's domain search and email finder compared walks through it.

A downloaded database is quicker for a one-off look, but it is a snapshot of one date, and a list of your own customers is rarely in it in full.

What does an enriched email record contain?

Two layers: a person block and a company block, in one response. We ran /combined/find on 2026-10-16 with a role address at a large payments company, so the fields below are what came back for that call. The person layer was almost empty and the company layer was full, which is the point of the next section.

The person block

person   id, name {fullName, givenName, familyName}, email, location, timeZone,
         utcOffset, geo, bio, site, avatar,
         employment {domain, name, title, role, subRole, seniority},
         facebook, github, twitter, linkedin, googleplus, gravatar,
         fuzzy, emailProvider, phone, indexedAt, activeAt, inactiveAt

For the role address, name and title were null and employment.role was support. For a personal address the same fields are where the name and seniority come from. fuzzy marks a looser match, so keep it next to the row and filter on it before you trust a name.

The company block

company  name, legalName, domain, domainAliases, description, foundedYear,
         category {sector, industryGroup, industry, GICS, SIC, NAICS},
         tags, location, geo, logo, phone, social handles,
         site {phoneNumbers, emailAddresses},
         metrics {employees (range), employeesCount, raised, trafficRank},
         tech, techCategories, fundingRounds, parent, indexedAt

Three fields change what you can build:

  • category and metrics.employees segment a list by industry and size with no parsing of free text.
  • tech and techCategories list detected technologies, so the same lookup tells you which accounts use a tool you integrate with.
  • indexedAt dates the record. On our call the company block carried 2026-06-18 while the person block carried 2026-10-17, so one response mixes ages.

What you can build with it

  • A cleaned CRM. Fill industry, size and country on every account from the domain, and leave a field blank when nothing came back. Missing data is easier to fix than guessed data, and the form enrichment post shows the same rule applied to a lead form.
  • A tiered outreach list. Segment by employee range and technology first, then run the person lookup only for the accounts you will actually contact.
  • A bounce-safe send list. Pair the enriched rows with a deliverability check before mail goes out; the verifier endpoints are in the table below.

What goes wrong when you enrich a whole email list?

Three things surfaced in our four calls, and each one changes the budget.

Why did a role address bill?

Because the endpoint answered with a record. info@ at a large company returned HTTP 200 with an empty person and a full company, and it billed one call at $0.0049 (Verified 2026-10-16). Our 404 did not bill, but a role address is not a 404 when the domain is known. So role addresses are not free to send through the person endpoint. Filter them before the loop (a deliverability check is a separate step, covered in choosing an email verification API), or send their domains through /companies/find, which gives you the same company block once per domain.

Why did a gmail address return an error?

Because free-mail addresses carry no company. A made-up gmail address answered HTTP 400 with invalid_email and the detail that it belongs to a webmail, and it did not bill. That is the useful outcome: the call tells you the address is outside the product. It is still better to filter free-mail in your own code, since a failed call takes a round trip and tells you nothing the domain did not.

What does an unknown address look like?

A 404 with not_found, and it did not bill. We ran a made-up address at the same large company and the detail text suggested enriching the company instead, which is what the domain-first order does anyway. Treat a 404 as "no person record", not "bad address": the address may be fine and simply absent from the index.

# keep the status next to the row so a blank is never ambiguous
monid run -p hunterio -e /combined/find --query '{"email": "someone@example.com"}' > out.json

Store an outcome per row (person, company only, webmail, not found). Four outcomes turn a list of blanks into a routing decision: the not-found rows are the ones worth a second provider.

How many calls is a whole list?

Count before you loop. For N unique domains and P personal addresses, the worst case is N plus P calls. Unique domains cost $0.0049 each (Verified 2026-10-16), so a list with 250 domains and 400 personal addresses is up to 650 calls, about $3.185. Skip the person step and it is 250 calls, about $1.225.

Which endpoint should I use for which job?

EndpointWhat it doesInputBillingPrice (Verified 2026-10-16)
hunterio/companies/findCompany record from a domaindomainPer call; a 404 did not bill in our run$0.0049
hunterio/combined/findPerson and company from one emailemailPer call; a role address billed$0.0049
hunterio/email-verifierIs this address deliverableemailPer definitive verdict$0.01225
contactout/v1/email/verifyDeliverability from a second provideremailPer definitive verdict$0.01
pdl/v5/person/enrichPerson record from an email, phone, name plus company or LinkedInPOST bodyPer call$0.28

Prices Verified 2026-10-16 with monid inspect; current figures are on monid.ai/tools. We ran the first two; the other three were inspected, not run. The person endpoints answer different questions at very different prices, so the order is cheap and wide first, dear and narrow last. For a second source on the personal addresses that came back not found, looking up the person behind an email goes through the same call in detail.

When is another route a better fit?

Your list is mostly free-mail. Gmail and Outlook addresses carry no company, so the domain step has nothing to work with. You need a name-based path, or a different source for the list.

You need a person for every row. An index does not hold everyone, and a 404 is a common answer for a personal address. Plan for a share of not-found rows and decide in advance what each one does next.

You want a screen to work in. Database platforms sell a workspace people open and a saved-search workflow. We ship endpoints, a CLI and an MCP server, so a person who wants a UI will prefer one of those.

Your list needs a compliance review. Enrichment joins public and collected data to personal addresses. Rules on that differ by country and by use, and you should check the ones that apply to you before running a list.

And the disclosure: we resell these endpoints. The useful part of this post costs nothing: sort the list in your own code, look up each domain once, and keep an outcome per row.

Conclusion

The quickest way to enrich a list of email addresses is to sort it for free, look up every company domain once, and run the person lookup last, through Monid: $0.0049 a call (Verified 2026-10-16), one key for this and every other tool, about a minute to set up and ready inside your agent whenever the next list arrives. A list of 1,000 addresses on 250 domains is about $1.225 for the companies.

Two things matter more than which endpoint you pick. A role address is not free: it came back as a 200 with a full company and billed, so filter it or send its domain to the company endpoint. And a blank is only useful when it says why, so store an outcome per row and let the not-found rows decide whether a second provider is worth buying.

How we estimated the time saved: writing the triage script, the domain dedupe, two enrichment loops and the merge back onto your rows is roughly 3 to 6 hours for someone who has not done it before. That is the range on this page. The 1 minute is the install, key and first call once you already have a Monid key.

Free next step: run monid inspect -p hunterio -e /companies/find, look up one domain, and compare the fields with your CRM. Start at monid.ai.

FAQ

Can you enrich a list of email addresses for free?

The sorting is free: the script above splits free-mail, role and personal addresses and finds the unique domains with no calls. The lookups are not free, but through Monid a company or person lookup is $0.0049 per call (Verified 2026-10-16), with no monthly plan and no minimum, and discovery and inspection cost nothing.

What is the cheapest order to run an email enrichment list?

Free sort first, company per domain second, person per address last. A company record is the same for every address at a domain, so one lookup per domain covers all of them, and the person step only needs to run for the addresses where you want a name or a title.

Does enrichment work on gmail and other free-mail addresses?

Not on the combined lookup. A made-up gmail address answered with a 400 saying it belongs to a webmail, and it did not bill. A free-mail address has no company behind it, so filter those in your own code and treat them as a separate list.

Do I pay when an email is not found?

Not on the 404 we ran: the unknown address answered 404 and did not bill. A role address is different. It returned a 200 with an empty person and a full company, and it billed one call, so filter role addresses before the person loop.

How do I get a CSV of enriched emails?

Each lookup returns JSON, so loop the endpoint over your list, keep the fields you want (company name, industry, employee range, country, title) and write them to a CSV with any tool, or ask your agent to build the table in the same prompt.

Published August 2026.

email enrichmentemail enrichment apienrich email listemail list enrichmentcompany data from email