Work Email Finder - Name + Domain to Email, Pattern & Score
Pricing
$2.00 / 1,000 per person resolveds
Work Email Finder - Name + Domain to Email, Pattern & Score
Give it a first name, last name and company domain; get back ranked work-email candidates. Each guess is built from REAL addresses observed at that domain (GitHub, npm, team pages) plus MX/SPF/DMARC, so the confidence score is earned, not invented. Source URLs on every row. No fake SMTP checks.
Pricing
$2.00 / 1,000 per person resolveds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
a day ago
Last modified
Categories
Share
π§ Work Email Finder β Name + Domain β Email, Pattern & Score
Give it a first name, a last name and a company domain. Get back a ranked ladder of work-email candidates β where the pattern was decoded from REAL addresses the company already published, with the source URLs on every row so you can check the answer yourself.
Most email finders hand you first.last@company.com and a confidence number they will not explain.
On the 16 domains where this actor found decodable evidence in its benchmark run, 8 of them do not
use first.last β Twilio, Cloudflare, HubSpot, Jones Day and Sidley use {f}{last}, McKinsey uses
{first}_{last}, Stripe leans {first}{last}. A ranker that defaults to first.last is wrong half
the time and never tells you.
This one shows its work:
| What you also get | Why it matters |
|---|---|
evidenceSamples[] | The actual Name β address pairs we found, each with the URL it came from |
patternVotes + patternAgreement | How the domain's addresses split across patterns β 100% or 43%? |
candidates[] | The full ranked ladder, because on a split domain the answer is often #2 |
mailDomain | mwe.com's people are reachable at mcdermottlaw.com β appending the input domain returns dead addresses |
| MX / SPF / DMARC / DKIM / BIMI / RDAP | The domain's whole public mail posture, on every row |
blockedSources[] | When a source blocked us we say so β "blocked" is never reported as "nothing found" |
π What it does
- DNS tier (always runs, direct, never proxied). MX, SPF, DMARC, BIMI and 18 DKIM selectors over DNS-over-HTTPS, plus RDAP registration facts. This tier alone guarantees every person gets a row.
- Evidence tier (public sources only). The company's GitHub org commits (author name + address),
npm maintainers/contributors, PyPI metadata, and the company's own website β homepage, then
robots.txtβ sitemap-discovered team / people / contact pages, plus/.well-known/security.txt. - Decode, don't guess. Every observed
Name β addresspair is tested against 20 pattern templates. A pattern only earns a vote if rendering it with that name reproduces the local part exactly.Michael Broshi β mbroshivotes{f}{last};David Brownman β xavdidvotes for nothing, and is reported as undecodable rather than forced into a bucket. - Rank and score. Confidence is
agreement Γ a sample-size factor, so a domain that splits 43% five ways cannot come back "high".
Sources verified live (2026-09-02/03, direct and through the Apify proxy)
| Source | Request | Result |
|---|---|---|
| DNS-over-HTTPS | GET cloudflare-dns.com/dns-query?name=stripe.com&type=MX (accept: application/dns-json) | HTTP 200, 517 bytes, 10 aspmx.l.google.com. |
| DMARC | GET .../dns-query?name=_dmarc.stripe.com&type=TXT | HTTP 200, v=DMARC1; p=reject; ... rua=mailto:dmarc-reports@stripe.com |
| RDAP | GET rdap.org/domain/stripe.com (follows redirect) | HTTP 200, 2,834 bytes, registration 1995-09-12T04:00:00Z |
| GitHub | GET api.github.com/repos/stripe/stripe-node/commits?per_page=100 | HTTP 200, 486,440 bytes, 56/100 commits from @stripe.com |
| npm | GET registry.npmjs.org/-/v1/search?text=stripe&size=20 | HTTP 200, 25,654 bytes, maintainer addresses at the domain |
| Website | GET www.mwe.com/people/ | HTTP 200, 369,600 bytes, 11 addresses, names recoverable from embedded JSON |
π₯ Input
| Field | Type | Default | What it does |
|---|---|---|---|
people | array (JSON) | β | The list to resolve: [{ "firstName":"Michael", "lastName":"Broshi", "domain":"stripe.com" }]. fullName works instead of first/last; companyDomain / website work instead of domain; a full URL is reduced to its host. |
personLines | array (strings) | [] | Paste-friendly alternative: Michael Broshi, stripe.com or Michael, Broshi, stripe.com. |
firstName / lastName / companyDomain | string | β | Convenience path for one person. |
useGithub | boolean | true | Read the company's public GitHub org commits. The richest source on technology companies; contributes nothing on a domain with no org. |
useRegistries | boolean | true | Read npm + PyPI package metadata. |
useWebsite | boolean | true | Read the company site: homepage β sitemap-discovered team/people/contact pages β security.txt. This is the tier that works on non-technology companies. |
maxWebsitePages | integer | 8 | Discovered pages to read per domain, on top of the homepage (0β30). |
maxGithubRepos | integer | 3 | Most-recently-pushed public repos to read 100 commits from (0β10). |
githubToken | string (secret) | β | Optional read-only GitHub PAT. Unauthenticated GitHub allows 60 requests/hour per IP; a token raises it to 5,000. No scopes needed. |
maxCandidates | integer | 8 | Length of the ranked candidates[] ladder (1β20). |
maxSecondsPerDomain | integer | 75 | Hard evidence budget per domain. When it expires the row still ships from what was gathered plus DNS/RDAP, and blockedSources says so. |
maxPeople | integer | 1000 | Safety cap on people per run (0 = no cap, 5,000 hard limit). |
proxyConfiguration | object | { "useApifyProxy": true } | Applies to GitHub / npm / PyPI / websites. DNS always runs direct. Switch to RESIDENTIAL if a company site refuses datacenter IPs. |
{"people": [{ "firstName": "Michael", "lastName": "Broshi", "domain": "stripe.com" },{ "firstName": "Jeff", "lastName": "Lawson", "domain": "twilio.com" }],"maxWebsitePages": 8,"maxGithubRepos": 3}
Evidence is cached per domain, so 50 people at one company cost barely more work than the first.
π€ Output β one row per person, 70 fields
Identity + answer
| Field | Type | Description |
|---|---|---|
firstName, lastName, fullName | string | Echoed from the input, so the row joins back to your CRM. |
companyDomain | string | The domain you supplied. |
inputIndex | integer | Position in your input list. |
email | string | The top-ranked candidate. |
emailPattern | string | The pattern that produced it, e.g. {f}{last}. |
confidence | number | 0β0.95. Driven by pattern agreement, damped by sample size. |
confidenceLabel | string | high β₯0.75 Β· medium β₯0.50 Β· low β₯0.30 Β· very-low below. |
confidenceReason | string | Plain English: what we proved and what we did not. |
matchType | string | observed-direct (we found this person's actual published address) Β· pattern-inferred Β· provider-heuristic (no evidence β generic prior). |
candidates[] | array | { email, pattern, score, votes }, best first. |
candidateCount | integer | Length of that ladder. |
The audit trail (this is the product)
| Field | Type | Description |
|---|---|---|
evidenceCount | integer | Real addresses observed at this company's mail domain. |
evidenceDecodedCount | integer | How many of those we could tie to a named person and decode into a pattern. |
patternVotes | object | pattern β vote count. An ambiguous pair splits its vote. |
patternAgreement | number | Top pattern's share of the vote. 1 = unanimous, 0.43 = a five-way split. |
evidenceSamples[] | array | Up to 12 { name, email, pattern, source, url, nameTrust, nameOrderAmbiguous } β the receipts. |
evidenceSources[] | array | github-commit, npm-package, npm-maintainer, pypi, website. |
evidenceUrls[] | array | Every URL we read for this domain. Open them and check us. |
patternSource | string | decoded-from-observed-addresses Β· structural-shape-of-observed-addresses Β· generic-prior. |
blockedSources[] | array | Sources that refused us (anti-bot interstitial, HTTP 403, GitHub rate limit, budget expiry). |
websitePagesRead | integer | Company pages actually read. |
Where the mail actually lives
| Field | Type | Description |
|---|---|---|
mailDomain | string | The domain the addresses are really on. |
mailDomainDiffersFromInput | boolean | true when it is not the domain you supplied. |
mailDomainBasis | string | Why we picked it. |
webDomain | string | Host the company website resolved to after redirects. |
hasMx, mxRecords[], mxHostCount | bool / array / int | MX hosts with priorities. |
mailProvider, mailProviderBasis | string | Google Workspace, Microsoft 365, Proofpoint, Mimecast, Zoho, Proton, Fastmail, Cloudflare Email Routing and ~20 more β plus the exact MX host or SPF include that identified it. |
spfRecord, spfIncludes[], spfPolicy, hasSpf | string / array | Full SPF record, its include: list, and the all qualifier (-all, ~all, β¦). |
dmarcRecord, dmarcPolicy, dmarcReportAddress, hasDmarc | string / bool | DMARC record, p= policy, and the rua= reporting mailbox. |
dkimSelectorsFound[] | array | Which of 18 common DKIM selectors publish a key. |
bimiRecord, bimiLogoUrl | string | BIMI record and logo, when present. |
catchAllLikely, catchAllBasis | bool / string | See honest limits β for Google/Microsoft domains this says no verifier can tell a real mailbox from a fake one here. |
Company + domain facts
| Field | Type | Description |
|---|---|---|
domainCreatedDate, domainAgeDays, domainExpiryDate | string / int | From RDAP. |
domainStatus[], domainRegistrarId, domainRegistrar | array / string | EPP statuses, IANA registrar ID and name. |
companyNameFromRegistry | string | RDAP registrant organisation. Redacted by ICANN privacy on effectively every gTLD β expect null. |
companyWebsiteStatus, companyTitle | int / string | HTTP status and <title> of the company homepage. |
robotsDisallowsPeoplePaths | boolean | Whether the site's robots.txt disallows people/team-shaped paths. |
githubOrg, githubOrgUrl, githubPublicRepos | string / int | The GitHub org we matched to this domain. |
securityTxtContact | string | Contact: line from /.well-known/security.txt. |
isRoleAddress, isFreeMailDomain, isDisposableDomain | boolean | Sanity flags on the chosen address and the mail domain. |
roleAddressesFound[] | array | Role mailboxes seen at the domain (info@, press@, β¦). |
Honesty fields
| Field | Type | Description |
|---|---|---|
smtpVerified | boolean | Always false. See honest limits. |
smtpVerificationAvailable | boolean | Always false. |
verificationMethod | string | Exactly what produced this answer. |
undeliverableReason | string | Set when the domain cannot receive mail (no MX, free-mail, disposable). |
resolvedAt, sourcesQueried[], lookupDurationMs | string / array / int | Provenance and cost of this domain's lookup. |
π Measured β run QOb1MsFAxLfhsskMe, build 0.1.4, 2026-09-03
Not estimates. This is one real Apify run at 1,024 MB through the Apify proxy.
| Metric | Value |
|---|---|
| Status | SUCCEEDED |
| People resolved | 30 across 26 domains |
| Runtime | 644.8 s |
chargedEventCounts | { "email-resolved": 30 } β billed == delivered |
| Fields per row | 70 |
| Fields at 100% fill | 39 |
| Fields at β₯50% fill | 64 |
| Confidence bands | high 9 Β· medium 4 Β· low 7 Β· very-low 10 |
| Match types | pattern-inferred 25 Β· provider-heuristic 5 |
Coverage β the number that matters, and it is not flattering
| Domain group | Any public address found | Address decoded to a pattern |
|---|---|---|
| Technology companies (11) | 11 / 11 | 10 / 11 |
| Non-technology companies (15) | 11 / 15 | 6 / 15 |
| All 26 | 22 / 26 | 16 / 26 |
The 10 domains with no decodable evidence still shipped a full row β MX, SPF, DMARC, DKIM, RDAP and a
ranked ladder β but flagged provider-heuristic / structural and scored very-low. That is the
correct answer, not a failure. Read the section below before you buy.
What the evidence actually said
| Domain | Addresses seen | Decoded | Agreement | Top pattern | Confidence |
|---|---|---|---|---|---|
| datadoghq.com | 23 | 14 | 100% | {first}.{last} | 0.95 |
| sentry.io | 20 | 12 | 100% | {first}.{last} | 0.95 |
| shopify.com | 28 | 14 | 100% | {first}.{last} | 0.95 |
| twilio.com | 22 | 9 | 100% | {f}{last} | 0.95 |
| elastic.co | 13 | 6 | 100% | {first}.{last} | 0.85 |
| hashicorp.com | 33 | 6 | 100% | {first}.{last} | 0.85 |
| mckinsey.com | 9 | 4 | 100% | {first}_{last} | 0.85 |
| vercel.com | 10 | 7 | 100% | {first}.{last} | 0.85 |
| mwe.com β mcdermottlaw.com | 10 | 7 | 86% | {f}{last} | 0.73 |
| cloudflare.com | 45 | 15 | 73% | {f}{last} | 0.70 |
| jonesday.com | 6 | 4 | 75% | {f}{last} | 0.64 |
| kirkland.com | 7 | 4 | 50% | {first}.{last} | 0.43 |
| sidley.com | 109 | 10 | 40% | {f}{last} | 0.38 |
| stripe.com | 95 | 7 | 43% | {first}{last} | 0.37 |
| hubspot.com | 40 | 1 | 100% (n=1) | {f}{last} | 0.35 |
| ferguson.com | 1 | 1 | 100% (n=1) | {first}.{last} | 0.35 |
Eight of these sixteen domains do not use first.last. That is the whole argument for this actor.
And note Stripe and Sidley: a big pile of observed addresses with low agreement scores low, because
the domain genuinely uses several conventions β the ladder in candidates[] is the answer there, not
the top row.
mwe.com is the case that breaks naive tools. McDermott's website is mwe.com; its people's
addresses are at mcdermottlaw.com. This run detected that, switched mailDomain, set
mailDomainDiffersFromInput: true, and decoded 7 addresses at 86% agreement. A tool that appends the
input domain returns undeliverable @mwe.com guesses.
Badge-risk preflight (timeout=300, 1,024 MB)
| Input | Runs | Result |
|---|---|---|
| Prefill-only (what Apify's daily health test sends) | 3 | SUCCEEDED, 2 items each β 18.0 s / 30.0 s / 42.7 s |
Empty {} | 2 | SUCCEEDED, 2 items each β 33.4 s / 75.4 s |
| Peak platform cost on any of those runs | β | $0.006409 |
Fleet health monitor: PASS, 19.1 s, 2 items.
π΅ Pricing
Pay per event β $0.002 per person resolved ($2.00 per 1,000). One event, email-resolved. Both
of Apify's auto-events (apify-default-dataset-item, apify-actor-start) are removed, so your bill
shows only people you actually resolved.
- Rows are shipped with
Actor.pushData(rows, 'email-resolved'), which is budget-aware: delivered equals billed, and a buyer who hits their charge cap never keeps rows they did not pay for. - A person we can produce no candidate for is delivered free (pushed without the event).
- Evidence is cached per domain, so the 50th person at a company costs the same as the second.
For comparison, measured from the Apify API on 2026-09-02: the largest actor in this lane charges $0.029 per email resolution (14.5Γ this), and a pattern-finder in the same lane bills $0.008 per pattern tested β roughly $0.064 for one person, and you pay for every wrong guess. This actor bills once per person and hands you the whole ladder.
β οΈ Honest limits β read these before you buy
1. There is no SMTP verification here, and smtpVerified is hardcoded false forever.
Outbound TCP port 25 is blocked on the Apify platform β measured 2026-09-02 from a real Apify run: MX
hosts on :25 returned ETIMEDOUT in ~260 ms while smtp.gmail.com:587 answered with a live 220
banner from the same run. And even where port 25 is reachable, it lies: during recon a deliberately
garbage mailbox, zzqqxx9988@stripe.com, got the same 250 2.1.5 OK from Google's MX as a real one.
Google Workspace and Microsoft 365 host most B2B mail and accept every recipient at the gateway. Any
product that calls those addresses "verified" is guessing with extra steps. We put that fact in
catchAllBasis on every affected row instead.
2. Coverage is tech-skewed. Measured above: 10 of 11 technology domains decoded, 6 of 15
non-technology domains. GitHub commits are the single richest source and a plumbing distributor does
not have a GitHub org. Non-tech domains fall back to the website tier, and if the company publishes
no addresses at all you get provider-heuristic at very-low confidence. That is what
cushmanwakefield.com, grainger.com, trane.com and wwgrainger.com returned in this run β a
complete DNS row and an honest "we have no evidence about this domain."
3. A correct pattern is not a live mailbox. We prove the domain's convention. We do not prove the
person still works there, that the mailbox exists, or that they do not use a nickname β observed in
a run against the stripe/stripe-python repos: David Brownman β xavdid@stripe.com and
Zachary Chua β zacchua@stripe.com. Both are reported pattern: null rather than guessed.
confidenceReason says this on every row.
4. Some sites block us, and we say so rather than reporting "nothing found." Measured on this
build: grainger.com returns HTTP 403 to the Apify container IP; mcdermottlaw.com (Imperva)
served a _Incapsula_Resource interstitial to a datacenter IP, to an Apify RESIDENTIAL IP and
to a plain home connection during testing β yet the same page returned 369,600 bytes of real content
on other attempts, so this is IP reputation and rate, not a permanent wall. Those rows carry
blockedSources[] naming the URL and the reason. Bring your own residential proxy via
proxyConfiguration if a target site refuses Apify's ranges.
5. GitHub's unauthenticated rate limit is 60 requests/hour per IP (measured:
X-RateLimit-Limit: 60githubToken for 5,000/hour. When the limit is hit the actor
degrades to the DNS + website tiers and records it in blockedSources β it never fails the run.
6. companyNameFromRegistry filled 0/30 in this run. ICANN privacy redacts the registrant
organisation on essentially every gTLD. The field is kept because some registries do publish it; on
.com/.org/.io expect null. Use companyTitle (86.7% fill) instead.
7. Name order from a web page is not evidence. Kirkland files Ken Au at /lawyers/a/au-ken, so
proximity-derived names can be surname-first. When a local part decodes under both orderings the vote
is split and both candidates appear on the ladder β which is why kirkland.com scores 0.43, not 0.85.
βοΈ Sources, robots and fair use
Every source this actor reads is public and unauthenticated: DNS records, RDAP registration data, GitHub's public API, npm and PyPI registry metadata, and the company's own public web pages. There is no login, no target-site cookie, no CAPTCHA solving, no named-crawler impersonation, and no LinkedIn scraping of any kind β LinkedIn discovery is the practice that ended Proxycurl, and this actor takes the name and domain from you instead.
The company-website tier reads robots.txt primarily to discover sitemaps, and the row reports
robotsDisallowsPeoplePaths so you can see whether a given site's robots.txt disallows the
people/team paths involved. Fleet policy is that a robots.txt Disallow is disclosed, not treated
as a licence question the actor decides on your behalf β the compliance call is yours.
Output is personal work-contact data. Depending on where you and your recipients are (GDPR, CAN-SPAM, CASL, PECR), you may need a lawful basis to process or contact these people, and you are responsible for that. This actor is built for legitimate B2B outreach, CRM enrichment and deliverability research. Do not use it for spam, harassment, or to contact private individuals.