DEV Community

Cover image for Bulk email finder in Claude Code: 200 profile URLs, 64 still current
Kevin Hall
Kevin Hall

Posted on Edited on

Bulk email finder in Claude Code: 200 profile URLs, 64 still current

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode
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>"
Enter fullscreen mode Exit fullscreen mode
{
  "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>" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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)