Bulk Email Finder from CSV | No Cookies
Pricing
from $0.70 / 1,000 email founds
Bulk Email Finder from CSV | No Cookies
Bulk email finder for CSV lists: paste rows, map your columns (first name, last name, domain), get one work email per person with status and confidence. Only addresses on the company domain are returned. Free plan: 100 rows per run. Pay per email found, misses free. No login or cookies.
Pricing
from $0.70 / 1,000 email founds
Rating
0.0
(0)
Developer
Open Enrich
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
8 days ago
Last modified
Categories
Features
Share
Bulk email finder for CSV lists: give your rows (first name, last name, company domain) and tell the actor which column is which, and get one work email per found person back, with a validity status and a 0-1 confidence score. Every result carries its rowIndex, so it joins straight back onto your spreadsheet. No login, no cookies, no start fee. You pay only for each email found.
No login | CSV rows + column map in, work emails out | rowIndex to join back | Status + confidence per row | Up to 5,000 rows per run | Misses are free | JSON/CSV export
Not an email verifier. This actor finds one work email per person. It does not check lists of emails you already have. Run your own deliverability check before a campaign.
💳 Pricing and plans
One event, email-found, charged once per row delivered. Prices per 1,000 emails found (set on the Pricing tab):
| Event | When it is charged | Free plan | Gold plan |
|---|---|---|---|
email-found | A work email on the domain in your row is found and the result row is delivered | $0.963 | $0.696 |
Bronze and Silver prices are on the Pricing tab. Higher Apify plans only lower the price. Empty runs, rows without a result, failed lookups, unreadable rows and notices are free, and there is no start fee.
Plan-credit math. The free plan's $5 monthly credit covers about 5,190 emails at the Free price.
Success-only billing
You are charged only for a result row you receive. A person who is not found, a row that cannot be read, a lookup that fails, or an address that is not on the domain you gave is listed in MISSES with its rowIndex and a reason and costs nothing.
The free plan
Free Apify accounts get up to 100 rows looked up per run, and as many runs as their Apify credit covers. Nothing is stored on our side. When a list is longer, the first 100 valid rows are looked up and one free notice row (reason: "free_plan_limit") says how many were not looked up; they are not charged. Run again with the rest, or use a paid Apify plan for no per-run limit.
Keep a run small while you test
Use limit to cap the number of rows looked up. The default is 100, the maximum is 5,000, 0 means all valid rows up to 5,000. Try 1 to 3 rows first, read the result, then scale up.
⚡ Why this bulk email finder?
- Your columns, your names.
columnMappoints first name, last name, full name, domain, company name and LinkedIn URL at whatever your CSV calls them. Leave a key out and common headers (First Name,Surname,Website,Company Domain) are detected. - Joins back to your CSV. Each result has
rowIndex(0 = your first row). Each row without a result is inMISSESwith the same index and a reason. - Only addresses on your domain. An address on any other domain is never returned as the result.
- Status and confidence on every row.
emailStatusisvalid,catch_allorunknown;invalidaddresses are not delivered.confidencefollows a fixed rule you can read below. - Success-only, no start fee. Misses, bad rows and notices are free.
🎯 Use cases: find emails from a spreadsheet
- Outbound prospecting. A conference or event list with names and company websites. Add the work email column.
- CRM gap fill. Contacts with a name and company but no email. Run the export and write the email back by row.
- Agent workflows. An AI agent holds a table of people and companies and needs addresses for all of them in one call.
- Pair with a phone lookup. Take the emails into email-phone-finder for direct dials.
🚀 Quick start: find work emails for a list
1. One row
{"items": [{ "First Name": "Patrick", "Last Name": "Collison", "Website": "stripe.com" }],"columnMap": { "firstName": "First Name", "lastName": "Last Name", "domain": "Website" },"limit": 1}
2. A list with a full-name column and extra columns
{"items": [{ "Contact": "Patrick Collison", "Site": "https://www.stripe.com/", "Account": "Stripe", "id": 101 },{ "Contact": "Satya Nadella", "Site": "microsoft.com", "Account": "Microsoft", "id": 102 }],"columnMap": { "fullName": "Contact", "domain": "Site", "companyName": "Account" },"limit": 0}
Columns you do not map (here id) are ignored.
3. No columnMap
{ "items": [{ "first_name": "Sam", "surname": "Altman", "Company Domain": "openai.com" }] }
⚙️ Inputs
| Key | Type | Default | What it does |
|---|---|---|---|
items | array of objects | required | One object per CSV row, your own column names as keys. Rows must be objects; a row that is text or empty is listed in MISSES as invalid_row. |
columnMap | object | auto-detect | Which of your columns holds what. Keys: firstName, lastName, fullName, domain, companyName, linkedinUrl. Values: your column header (case, spaces and punctuation do not matter). A mapped column that no row has stops the run before any charge. |
limit | integer | 100 | Maximum rows looked up, which also caps the emails charged. 0 = all valid rows. Maximum 5,000. |
includeAudit | boolean | false | Adds an audit object to each result. |
How a row is read: the name is first name + last name when both are filled, otherwise the full-name column. Both a first and a last name are required; a one-word name or a bare email address is not a name. The domain can be a plain domain or a website URL (https://, www. and paths are removed). The lookup uses the name and the domain. companyName and linkedinUrl are copied into inputQuery so you can trace a result to your own record.
Any other field stops the run before a lookup with remove unsupported fields: .... Nothing is charged.
📤 Output
One result row per CSV row whose email was found on the domain in that row. Results keep input order. Empty values are left out.
| Key | Type | Meaning, and when it is missing |
|---|---|---|
rowIndex | integer | Position of the row in your items (0 = first). Use it to join back |
fullName, firstName, lastName | string | As matched, otherwise as you gave them |
email | string | Work email on your domain, lower case |
emailStatus | string | valid, catch_all or unknown. unknown is common: the address is not mailbox-checked. invalid is never delivered |
confidence | number | 0-1, see the rule below |
matchMethod | string | How the person was matched. Missing when not reported |
companyDomain | string | The domain from your row, cleaned |
slug, profileUrl | string | Professional profile slug and link, when matched |
headline, jobTitle | string | Current, when on file |
location, country | string | Free-text location and ISO-2 country, when on file |
connections | integer | Connection count, when on file |
updatedAt | string | When the person record was last updated, when on file |
inputQuery | object | The cleaned input behind this result: fullName, domain, and your companyName and linkedinUrl when mapped |
scrapedAt | string | ISO time the row was produced |
audit | object | Only with includeAudit |
{"rowIndex": 0,"fullName": "Patrick Collison","firstName": "Patrick","lastName": "Collison","email": "patrick.collison@stripe.com","emailStatus": "unknown","confidence": 0.5,"matchMethod": "work-email","companyDomain": "stripe.com","slug": "patrickcollison","headline": "Stripe CEO","jobTitle": "CEO","location": "San Francisco Bay Area","country": "US","connections": 500,"updatedAt": "2026-10-13T14:15:13.902Z","inputQuery": { "fullName": "Patrick Collison", "domain": "stripe.com" },"scrapedAt": "2026-10-28T09:00:00.000Z"}
Which fields appear depends on what is on file for the person.
Confidence rule
Base by status: valid 0.95, catch_all 0.6, unknown 0.5. Add 0.05 only when the response carries a profile link (profileUrl). Maximum 1. A catch_all domain accepts any address, so even a good status there is a weaker signal.
Other records in the run
MISSES(key-value store):[{rowIndex, input, reason}]for every row without a result. Reasons:not_found,no_address_on_domain,missing_name,missing_domain,invalid_name,invalid_domain,invalid_row,duplicate_row(the same person and domain as an earlier row;duplicateOfnames that row), orUpstream HTTP <code>.errors-bulk-email-finder(key-value store): the lookups that failed for a technical reason, with theirrowIndex, so you can re-run only those rows.RUN_SUMMARY: counts (requested, valid, looked up, found, missed, rows not looked up because oflimitor the free plan) and the stop reason.- Notice rows (
type: "notice") appear when a free-plan list is longer than 100 (free_plan_limit), when the spend limit of your run is reached (charge_limit_reached), or when the input is invalid (invalid-input). Notices are never charged.
What is NOT returned
- Personal email addresses
- Phone numbers (use email-phone-finder)
- Several alternative addresses per person
- A live mailbox check of the address
🛡️ Reliable results
Our data promise. We work to keep every contact current, and we stand behind it. If a record is outdated, an email bounces or a person has moved on, open the Issues tab with your run link. We review every report and sort it out with you, case by case.
- Misses are free and visible. Every row without a result is in
MISSESwith itsrowIndexand a reason and is never charged. - Only addresses on your domain. An address on another domain is never returned as the result.
- Duplicate rows are looked up once. The same person and domain written twice is looked up and charged once; the repeat is in
MISSESasduplicate_row. - Each result is saved as it is delivered. A run that stops keeps what it already produced.
- Bounded pace. Lookups run at 60 per minute, so 1,000 rows need roughly 17 minutes and a full 5,000-row run about 83 minutes.
- Typos fail early. An unsupported input field, a wrong column name in
columnMapor a limit above 5,000 stops the run before any charge. - Check before you send.
emailStatusandconfidenceare signals, not a guarantee. Run the emails through your own deliverability check before a campaign.
🤖 For AI agents (MCP)
This actor runs through the Apify MCP server. Prompt to copy:
"Use b2bdata/bulk-email-finder with items=[{"First Name":"Patrick","Last Name":"Collison","Website":"stripe.com"}], columnMap={"firstName":"First Name","lastName":"Last Name","domain":"Website"} and limit=1. Return rowIndex, email, emailStatus and confidence."
When to use it. You hold a table of people with first and last names and company domains and need one work email each.
When not to. You hold one person (use b2bdata/email-finder), only a company (use ceo-founder-email-finder-by-domain), or only an email (use email-phone-finder).
Input contract. items array of row objects; columnMap object (optional); limit 0-5,000 (default 100, 0 = all).
Output contract. One result per found row with rowIndex, email, emailStatus, confidence. Output is deterministic and schema-stable, results keep input order. Rows without a result are absent from the dataset and listed in MISSES.
Smoke test. limit: 1 on a well-known person returns one result.
Error cases. No valid rows means no row had both a name and a domain. remove unsupported fields or a columnMap problem ends the run SUCCEEDED with one free invalid-input notice row and nothing charged.
🔌 Integrations: send results anywhere
- API. Fetch results from the run's default dataset:
GET /v2/datasets/{datasetId}/items?format=csv. - Webhook on run success to push results to your own endpoint.
- n8n, Make, Zapier. Use the native Apify nodes: run the actor, then read the dataset.
- Google Sheets, Airtable, HubSpot, Salesforce, Clay. Join on
rowIndex, mapemailto the email field andemailStatusto a status column.
🕐 Schedule examples
- Weekly CRM gap fill. A Monday schedule with a saved task that points at your latest export.
- New-lead enrichment. A webhook from your form tool starts a run with the new rows.
🔗 Related actors
b2bdata/email-finder: one person at a time, same lookup. Openb2bdata/ceo-founder-email-finder-by-domain: you know the company, not the person. Openb2bdata/executive-contact-finder-by-domain: get the decision makers at a domain first. Openb2bdata/email-phone-finder: you have the email and need a phone. Open
❓ FAQ
Data and coverage
How do I find emails for a CSV list?
Put your rows in items (one object per row, your column names as keys), map the columns in columnMap and run. Each found person becomes one result with the email, its status and a confidence score.
Can I use my own column names?
Yes. columnMap maps firstName, lastName, fullName, domain, companyName and linkedinUrl to your headers. Leave a key out and common header names are detected.
How do I match the results back to my spreadsheet?
Every result has rowIndex, the position of the row in your items (0 = first). Rows without a result are in MISSES with the same index.
Do I need a LinkedIn account or cookies? No. It is a work email finder without login, cookies or an account to ban.
Is there a confidence score per row? Yes. The score follows the fixed rule in the Output section: status first, plus a small bonus when the person was matched to a profile. It is a signal for sorting, not a promise.
Pricing
What do I pay when nobody is found? Nothing. There is no start fee, and misses, unreadable rows, duplicates and notices are free.
🛟 Support
Use the Issues tab on this actor's Apify page for bugs, questions and feature requests. We answer within 48 hours on working days. Please include the run ID and the input you used. Published on Apify by b2bdata. Not affiliated with, endorsed by, or sponsored by any company or person named in the examples above.