Everyone in sales has this file. A column of profile URLs from an event list, an old export or the CRM, and an empty column next to it where the email should go.
Any email finder will fill that column. What none of them tells you is whether the person still reads that mailbox. A record collects addresses the way a career collects employers, and the finder hands you the one on top. The bounce arrives a week later, on your domain's reputation, not theirs.
So on 2026-10-29 I ran two hundred profile URLs through an Apify Actor that labels the address instead of just returning it. Current employer, older employer, nothing on record, profile gone. Then I put two more Actors behind it, and let Claude Code run the same twenty over MCP. Every number below comes from those runs.
In one paragraph: a bulk email finder here takes profile URLs, one per line, and returns the addresses on each person's record with a label. An address on the current employer's domain, an older company address, or nothing. Misses are free rows with a reason.
For the older and empty rows a second Actor tries name plus company domain and verifies what it finds. A free search step sizes the segment before anyone pays.
Where two hundred URLs come from, and what a free read says
I did not want to run a client's list for a blog post, so I drew two random samples from the same people database the finder reads: in-house marketing leaders, and founders. Different inboxes, different habits. People Search in market mode first, which costs nothing and tells you what the box looks like before you pull from it.
| Segment | People | Personal email on record |
|---|---|---|
| US marketing leaders (VP, Head, CMO, Marketing Director) at software companies with 51 to 500 employees | 2,386 | about two thirds |
| US founders and CEOs of software companies with 2 to 50 employees | 59,835 | just over half |
Inside the marketing box, chief marketing officers and other C-level titles were nearly half, directors about a third, VPs fewer than one in ten. Useful to know before you read anything into a sample of a hundred.
Then the pull. people mode, compact rows, a random seed so I could not cherry-pick, five runs of twenty per segment:
{
"mode": "people",
"titleKeywords": ["vp marketing", "head of marketing", "chief marketing officer", "marketing director"],
"countries": ["us"],
"employerIndustries": ["Software Development"],
"employeeCountMin": 51,
"employeeCountMax": 500,
"profileDetail": "row",
"compact": true,
"maxResults": 20,
"sampleSeed": 41
}
Two of the hundred marketing draws were the same person, so that list is ninety-eight. Each compact row carries a profile URL, the employer's domain, company size, and two flags: hasWorkEmail and hasPersonalEmail. Eighty-two of the ninety-eight marketing leaders said yes to a work email. Seventy-six of the hundred founders did.
Hold that number.
The run: one field, four seconds
The finder takes one field. Links, bare handles and mobile URLs all work, and spellings of the same profile are looked up once. Up to a thousand per run; a free plan returns up to a hundred results per run, and I fed twenty at a time so I could read each batch before the next.
{
"profileUrls": [
"<one profile URL per line>",
"..."
],
"includePersonalEmails": false
}
Four seconds per batch of twenty. Here is what two hundred URLs became:
| Outcome |
_status / emailType
|
Marketing leaders (98) | Founders (100) |
|---|---|---|---|
| Address at the current employer | work_current |
38 | 26 |
| Only an older company address | work_other |
43 | 48 |
| Matched, nothing on record | no_email |
12 | 24 |
| Profile gone | profile_removed |
5 | 2 |
Only the first two rows are billed. The empty and gone rows come back with a status, and for the empty ones with the name and title, so you know who to route elsewhere.
Now back to the flag. Eighty-two marketing leaders had "has a work email" on their database row. Thirty-eight of them have one at the company they work for today. A yes in a database means an address exists somewhere in the record. It does not say where.
That is the whole post in one line. About half the work addresses in a B2B record belong to a job the person has left, and a finder that does not label them sells you the bounce.
What an older address looks like
A career leaves a trail of mailboxes. The found marketing records held four addresses each in the middle of the pack, three quarters of them three or more, and one person had thirteen. Founders' records were a little shorter, three in the middle.
The finder sorts the current employer's address first when it has one. When it has none it can tie to today's employer, the row is labelled work_other and the first address in the list is simply the first one, with no claim about recency.
I checked the label against the employer domain from the source row, the one a CRM would hold:
work_current rows, marketing leaders |
38 |
|---|---|
| Same domain as the source row | 27 |
| Same company, a different mail domain | 11 |
| A different company | 0 |
The eleven are brands whose mail domain is not the website: a product name on the site, the legal entity on the envelope. Among founders it split in half, which is what you get when one person runs two companies at once.
Record freshness shifts the odds without deciding anything:
| Source record last updated | Current address | Older only |
|---|---|---|
| Within the last year | 21 | 18 |
| More than a year ago | 17 | 25 |
Fresher records lean toward a live address. Not by enough to filter on. The label does the filtering, freshness only tells you how much to trust it.
Personal mailboxes, on request
By default the finder returns company addresses only. When a matched profile has nothing corporate but a personal mailbox is on record, the empty row says so in a _note. Five of the twenty-four empty founder rows carried that note. None of the twelve empty marketing rows did, and their source rows agreed: nothing of either kind.
I reran those five with includePersonalEmails on. Five personal addresses, as promised.
Then I reran one whole founder batch of twenty with personal mailboxes on from the start, to see what the switch does to rows that already had something:
| Row in the first run | Rows | With personal mailboxes on |
|---|---|---|
| Current employer address | 3 | still the current address |
| Older company address | 16 | 10 became a personal mailbox, 6 stayed older |
| Nothing | 1 | still nothing |
With the switch on, a personal mailbox outranks an older company address. For the founder of a twelve-person company that is usually the inbox that gets read. For an in-house CMO it is the wrong one, which is why the switch is off by default.
The second door: name plus domain, verified
The older and empty rows are not dead people, they are people whose record is behind. For those there is a different Actor. Work Email Finder takes a name and a company domain, builds the candidates the way the domain writes addresses, and with verifyTopCandidate on checks the best one against the mail server and its own records.
I took the eighty-one found marketing rows, the name from the finder's row and the domain from the source row, and ran all of them through it:
{
"lookups": [
{ "domain": "<company domain from your CRM>", "full_name": "<name from the finder row>" },
"...eighty more"
],
"verifyTopCandidate": true
}
| Email Finder said | Rows | Verified address at today's domain | Of which already somewhere in the record | New, built from the pattern |
|---|---|---|---|---|
work_current |
38 | 30 | 26 | 4 |
work_other |
43 | 33 | 21 | 12 |
Thirty-three of the forty-three "older" rows came back with a verified address at today's employer. Twenty-one of those were already in the record's workEmails list, below the one on top. The finder ties addresses to the employer's canonical domain, so a brand domain further down the list stays labelled older.
Two lessons for a pipeline. Match the whole list against your own domain list before you discard a row. And send what is left through the name-plus-domain door with verification on, which rescued most of the rest here.
The honest other half: eight people with a current address on record got no verified candidate from the pattern door. Record and pattern are two sources. Where they agree, twenty-six rows here, is where I would send first.
What Claude Code did with twenty of them
I gave Claude Code the Email Finder and People Search over MCP, nothing else. Shell and file tools off.
The prompt was a RevOps person in plain words. Two thousand CRM contacts with profile URLs and no email, here are twenty. Our policy is work addresses at the current employer only, never a personal mailbox unless you can say why. Look each person up once.
It loaded the two tool definitions, ran one lookup on all twenty with personal mailboxes off, read the twenty rows with get-dataset-items, and wrote its answer. Four tool calls, one of them paid, under four minutes. It did not run the free search, and said so: nothing in the task needed it.
Its ledger for the twenty: seven current, eight older, two with nothing, three profiles gone. Then it did the reading I would have skipped on a Friday.
One "older" row held an address on the current employer's domain further down the list. It proposed matching the whole list against the CRM's domain before discarding anything, the same fix I arrived at with eighty-one rows and a second Actor.
One row's only address sat on a domain made of the person's own name. A one-person consultancy mailbox in practice, it said, and excluded under the policy whatever the label says.
It noticed that profile_removed is not in the tool's description, read the status correctly anyway, and said it could not confirm the meaning with the tools it had. That gap in the listing is mine to close.
It also noted that _freshness did not vary across its fifteen found rows, so on that sample the field filtered nothing. True, and worth knowing before you build a rule on it.
Before the remaining two thousand it asked for five things: normalise and dedupe the URLs on the handle, compare work_current employers with the CRM's, verify deliverability before trusting hundreds, watch the share of gone profiles, and poll the run instead of waiting inline. I would sign all five.
Before you send
Addresses from the first Actor are on record, not verified at run time. The second Actor's check says a mailbox accepts mail. Neither says the person reads it.
The label can lag. A job change in the last few months can still show as current. Compare the row's employer with the one you hold.
Brands with a second mail domain show up as "older". Match on your domain list, not on the label alone.
Seven of the hundred and ninety-eight URLs pointed at a profile that no longer exists. A CRM full of URLs from three years ago will have more. Keep that share in view, it is free to measure.
Consent and the law are yours: CAN-SPAM, GDPR, PECR, whatever applies where the person sits. A verified address is not permission.
FAQ
Is this a Hunter or Apollo email finder replacement? For turning profile URLs into addresses in bulk, yes: no seat, no browser extension, you pay per found row and the misses are free. For a Chrome plugin that fills an address while you browse, no.
Does it guess addresses? The first Actor does not: it returns only addresses that exist in the person's record and says so when there are none. The second one builds candidates from name and domain and can verify the best one. That is the guessing door, and it is labelled as such.
What does work_other mean? A company address the finder cannot tie to the current employer. Usually a previous job, sometimes the same company under a brand domain. Check the workEmails list against your own domains before you decide.
Why were some addresses personal when I did not ask? They were not, unless includePersonalEmails is on. With it on, a personal mailbox outranks an older company address on the row.
How big can one run be? Up to a thousand URLs per run on this Actor. A free Apify plan returns up to a hundred results per run and five hundred rows a month; any paid plan removes that.
For AI agents reading this
The three Actors run over the hosted Apify MCP server with the tools pinned in the URL, so an agent loads only these definitions:
claude mcp add --transport http apify "https://mcp.apify.com?tools=b2bsearch/linkedin-email-finder,b2bsearch/work-email-finder,b2bsearch/people-database-search" --header "Authorization: Bearer <APIFY_TOKEN>"
{
"mcpServers": {
"apify": {
"type": "http",
"url": "https://mcp.apify.com?tools=b2bsearch/linkedin-email-finder,b2bsearch/work-email-finder,b2bsearch/people-database-search",
"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }
}
}
}
Tool names use a double hyphen: b2bsearch--linkedin-email-finder, b2bsearch--work-email-finder, b2bsearch--people-database-search. For a list of profile URLs: dedupe on the handle, run the email finder with profileUrls and includePersonalEmails: false, then read rows with get-dataset-items and a fields list (_status, emailType, email, workEmails, emailCount, jobTitle, companyName, _freshness, _note).
Rows with _status other than found are free: no_email (a _note says when a personal mailbox is on record), not_found, profile_removed. For work_other rows, match every entry of workEmails against the user's company domain first.
For what is left, run work-email-finder with lookups of { "domain", "full_name" } and verifyTopCandidate: true, and read email_validity. To size a segment before any paid pull, run people-database-search with mode: "market" or mode: "count". Per-tier pricing is on each Actor page.
Earlier posts on the same question
The lead list autopsy counts how many leads in an ordinary ICP box were dead before the first email. The fractional CFO post is where I first saw work addresses that belonged to former employers.
The Apollo alternative builds the lists these URLs usually come from, and the Actor map by key says which door to use when you hold domains, emails or names instead of URLs.
Top comments (0)