# B2B Email Finder & Verifier - Find Emails, LinkedIn & Phones (`memo23/email-finder`) Actor

Find emails from a name + company domain, a LinkedIn profile, or a whole company, in bulk. Pick the decision-makers you want (founder, CEO, VP) and get names, titles, phones and B2B emails ranked by confidence. An optional SMTP check confirms the inbox exists. Pay only for verified contacts.

- **URL**: https://apify.com/memo23/email-finder.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Lead generation, AI, Agents
- **Features:** Limited permissions
- **Stats:** 435 total users, 117 monthly users, 99.9% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $10.00 / 1,000 company enricheds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Email Finder & Verifier - Find Decision-Makers & Verify Their Emails

<p align="center">
  <img src="https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/email-finder-logo.png" alt="Email Finder logo" width="220" />
</p>

**Find the decision-makers at any company for free - then pay only for the ones that resolve to a verified, deliverable email.** Give it a company domain and the roles you want (founder, CEO, VP, head of sales, marketing lead); it reads the team, about, and leadership pages, pulls the real people with their titles, infers each business address, cross-checks it against the emails the company actually publishes, and asks the company's mail server which address really exists. **You bring a domain, not a spreadsheet of names** - the actor finds the people first. Finding them costs **nothing**; a mail-capable person-email is **$0.02**, and one the mail server confirms is **$0.05** in total. Every result ships with the ranked candidate addresses, the mail provider, and a confidence score - not a single blind guess, and no second verifier to wire up.

**The whole funnel, in one line:**

```
Discover people  →  infer + rank addresses  →  site cross-check + SMTP mailbox check  →  you pay only on a mail-capable hit
   (free)                  (free)                          (free)                     ($0.02, or $0.05 when the server confirms the mailbox)
```

| Input | Row(s) emitted |
| --- | --- |
| **Discovery query (roles × company / location)** | One `discovered-person` row per person found - name, title, LinkedIn + verified email. **Finding is free; you pay only for verified hits.** |
| Domain or company name | One company row - ranked emails + phones, provenance, confidence |
| Person (`fullName` + `companyDomain`) or a LinkedIn `/in/` URL | One `person` row - best email (mailbox-confirmed when the server says so), ranked candidates, MX + provider |

> Pure HTTP. No API keys, no LinkedIn login, no cookies - and no 5,000-row dumps of unverified guesses you pay for by the row.

### Why Use This Scraper?

- **Find free, pay only for verified.** Discovering who works at a company - and inferring, ranking, and MX-checking their addresses - costs nothing. You're billed **only when a person resolves to a mail-capable business email** ($0.02, plus $0.03 when the mail server confirms the exact mailbox). A run full of misses costs $0, and so does a person whose likely addresses the mail server all refuses. That's the opposite of bulk dumpers, who bill for every unverified row they emit.
- **It finds the people - the cheap finders make you bring them.** Point it at a domain and a role and Discovery returns the named decision-makers *and* their verified emails in one run. The budget name-and-domain finders need you to already have every person's first name, last name, and company before they start; the resold B2B databases hand you stale rows someone else pulled months ago. Here the finding is live, done for you, and free - you pay only on a verified hit.
- **You verify AND rank - most finders just hand you an address.** Every person row is MX-checked, the top candidates are asked of the company's mail server over SMTP until one is accepted (`mailboxConfirmed: true`), and the result is cross-checked against the addresses the company actually publishes (a published match returns as `site-match`, high confidence), and comes with the **full ranked candidate list** (`first.last@`, `flast@`, `first@`, …) plus scores, the mail provider (Google Workspace, Microsoft 365, …), and a `source`/`confidence` pair. You see how each answer was derived, not just the answer. The SMTP check runs on our own mail-check server - there's no second verifier actor to set up or pay for on your own account.
- **Target the decision-makers, by role.** Pick a preset with `targetPersona` - Founder/CEO, Sales leader, Finance leader, and so on - and the actor turns a plain list of domains into a decision-maker search, no per-company query to write. Or filter discovery by exact job title (`["founder", "ceo", "vp", "head of sales"]`) when you want fine control. Either way you get named executives with titles and verified emails, not a generic `info@`.
- **No API keys, no login, no cookies.** The core path - team-page discovery, site scrape, pattern inference, MX verification - is fully self-contained. Nothing to sign up for, no LinkedIn session, no headless browser. (Optional keys only *add* coverage: RocketReach for open-web search, Hunter.io for a domain-search fallback.)
- **Phones included.** Company rows carry every phone number harvested from the same contact pages - no extra charge, no second tool.
- **Bulk by design.** Paste arrays, spreadsheet columns, or a comma-separated blob of hundreds of companies; mix companies, people, and discovery queries in one run.
- **Monetization qualification for free.** Flip `qualifyByPayment` and each company row is flagged with `takesPayments` + `paymentProcessors` (Stripe, Shopify, PayPal, Paddle, …) - reusing pages already fetched, zero extra cost.

### Overview

Email Finder is a **find-and-verify lead engine**. The first three modes mix freely in one run; the fourth is a standalone verifier:

- **Discovery** - the funnel. You know only the role/company/location; the actor finds the people (company team pages by default, open-web RocketReach search with your own API key), then resolves each person to a verified email. Discovery itself is **free** - you're billed only per verified email found.
- **People** - you already know the person; `{fullName, companyDomain}` returns their most-likely business email with ranked candidates, MX verification, and site cross-checking.
- **Companies** - by domain (Track A: the site is scraped directly, most accurate) or by name (Track B: the website is resolved first behind a name-match precision guard, then scraped). Output: ranked emails + phones per company.
- **Verify-only** - paste a raw list of emails and the actor validates each (syntax, MX, disposable/role/free, and a valid/risky/invalid verdict) and exits. A bulk email verifier when that's all you need - billed $0.001 per address.

A realistic note on hit rates: on-page emails exist for companies that publish a contact address - most small and mid-sized businesses, agencies, shops, and firms. Discovery finds people on sites that publish a team/about/leadership page (very common for agencies, startups, professional services); enterprises that hide both go through the person-inference or BYO-key paths. Every row tells you exactly where its answer came from, so quality is never a guess.

### Supported Inputs

Mix and match in one run - all merged and de-duplicated:

- **`domains`** - company websites/domains (Track A, most accurate).
- **`companyNames`** - company/brand names to resolve, then find emails (Track B).
- **`inputText`** - paste a blob of domains or names (new-line / comma / semicolon separated).
- **`companies`** - advanced structured rows mixing `name` + `website` + `city`/`state`.
- **`people`** - person-level lookups: `{ "fullName": "Patrick Collison", "companyDomain": "stripe.com" }`, `"Patrick Collison, stripe.com"`, or a LinkedIn profile URL (`https://www.linkedin.com/in/satyanadella/`).
- **`linkedinUrls`** - public `linkedin.com/in/…` links only. Same path as People: public card → name + employer → inferred email.
- **`peopleDatasetId`** - an Apify dataset of people (name + domain, or a `/in/` URL), for example the output of a LinkedIn or Maps run. Company-only rows are skipped.
- **`discoverPeople`** *(new)* - discovery queries: `{ "website": "stripe.com", "jobTitles": ["engineering"] }`, `{ "company": "Notion", "personName": "Ivan" }`, or with a RocketReach key `{ "jobTitles": ["CTO"], "locations": ["Austin"] }`. Plain strings work too: `"CTO @ stripe.com"`.
- **`emailList`** *(verify-only)* - paste emails you already have to validate them. When set, the actor runs in **verify-only** mode (no finding): each email gets syntax, MX, disposable, role-based and free-provider checks plus a **valid / risky / invalid** verdict (and an `smtp` object when `smtpCheck` is on). Billed per email via `emails-verified` ($0.001).

#### Verify a list of emails

```json
{
    "emailList": ["jeff@amazon.com", "noreply@stripe.com", "test@mailinator.com"]
}
```

Output per email:

```json
{ "mode": "verify", "email": "noreply@stripe.com", "syntaxValid": true, "domain": "stripe.com", "mxFound": true, "isDisposable": false, "isRoleBased": true, "isFreeProvider": false, "verdict": "risky" }
```

### Use Cases

| Who | What they use it for |
| --- | --- |
| **Sales & lead-gen teams** | Turn a target-account list into verified decision-maker emails - including accounts where they don't know a single name yet. |
| **Agencies & freelancers** | Build a niche prospect list (dentists, law firms, e-commerce shops) from names or domains, with phones. |
| **Recruiters** | Find the hiring manager or team lead at a company (Discovery by title), then their verified email. |
| **Market researchers** | Attach contact emails, phones, and resolved websites to a company list for a study or directory. |
| **RevOps / data teams** | Enrich a CRM segment with verified emails, provenance, and payment-stack qualification in bulk. |

### How It Works

![How the Email Finder works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-email-finder.png)

1. **Input** - companies (domains / names / pasted blob), known people, and/or discovery queries (title × company × location).
2. **Discover** *(Discovery mode)* - the company's team, about, and leadership pages are scanned for real people (JSON-LD Person schema, LinkedIn profile cards, team-card markup - strict name validation, no fabrications). With your own RocketReach API key, open-web search by title/location runs instead, using only the free search teaser.
3. **Resolve & verify** - every person is resolved to their most-likely address: all standard corporate patterns generated and ranked, the domain MX-verified, the mail provider detected, the result cross-checked against emails actually published on the site (a published address = `site-match`, high confidence), and the top candidates (up to 4) asked of the mail server until one is accepted. Catch-all domains and servers that won't answer keep the ranked guess. Company rows get their contact pages scraped, junk stripped, role emails ranked first, phones harvested - with optional per-address verification.
4. **Export** - one flat row per company / person / discovered person, streamed to the dataset: JSON, CSV, Excel, or API.

### Input Configuration

| Field | Type | Description |
| --- | --- | --- |
| `domains` | array | Company websites/domains to find emails for (Track A, most accurate). |
| `companyNames` | array | Company names to resolve to a website, then find emails (Track B). |
| `targetPersona` | array | **Decision-maker presets** - pick roles (Founder/CEO, Full C-suite, Sales leader, Marketing leader, Engineering leader, Finance leader, HR leader, Operations leader) and the actor auto-runs a people search for each `domain`/`companyName`. Turns a plain domain list into a decision-maker search - no per-row `discoverPeople` query needed. |
| `defaultCountry` | string | Country hint applied to every persona search (e.g. `United States`). |
| `inputText` | string | Paste a blob of domains or names (new-line / comma / semicolon separated). |
| `companies` | array | Advanced - structured `{ name, website, city, state }` rows. |
| `people` | array | Person lookups - `{ fullName, companyDomain }` objects or `"Name, domain.com"` strings. |
| `discoverPeople` | array | **Discovery queries** - `{ company?, website?, personName?, jobTitles?, locations?, maxPeople? }` objects or `"CTO @ acme.com"` strings. Finds the people, then resolves their emails. `locations` applies on the RocketReach path (team pages don't publish per-person locations). |
| `rocketreachApiKey` | string | Optional official RocketReach API key - upgrades Discovery to open-web search by title/location/industry with no company needed. Search teaser only: **your per-contact unlock credits are never spent**. |
| `maxDiscoveredPeople` | integer | Global cap on people processed by Discovery per run (default 200; per-query default 25 via `maxPeople`). |
| `guardMode` | string | Domain-match strictness for name→website discovery: `business` (default), `off`, `residential`. |
| `onlyWithEmails` | boolean | **On by default** - emit and bill only company rows where an email was found, so you never pay for a no-email company. Set `false` to also get no-email rows (for coverage auditing); those are billed when emitted. |
| `verifyEmails` | boolean | Validate every found company email (syntax + MX + disposable/role/free flags); adds `emailVerifications`, `validEmails`, `bestValidEmail`. Billed per email verified. |
| `smtpCheck` | boolean | On by default. Real SMTP mailbox check on top of verification (EHLO → MAIL FROM → RCPT, plus catch-all detection), run from our own mail-check server because Apify blocks the SMTP port. Adds an `smtp` object and `mailboxConfirmed` per checked email, `confirmedEmailCount` per company, and ranks confirmed addresses first in `validEmails`. An unreachable or rate-limited server defers to the DNS/MX verdict; only a clean 550 marks an address invalid. No extra charge. |
| `qualifyByPayment` | boolean | Flag each company with `takesPayments`, `paymentProcessors` (Stripe/Shopify/…), `stripeLiveKey` - free, reuses pages already fetched. |
| `hunterApiKey` | string | Optional Hunter.io API key for a paid domain-search fallback. |
| `maxItems` | integer | Hard cap on companies processed this run. |
| `maxConcurrency` | integer | How many jobs to run in parallel. |
| `proxy` | object | Leave empty - traffic already routes through the actor's built-in residential proxy at no extra cost. |

#### Example input

```json
{
  "domains": ["ahrefs.com", "sitepoint.com"],
  "companyNames": ["Basecamp"],
  "people": [{ "fullName": "Patrick Collison", "companyDomain": "stripe.com" }],
  "discoverPeople": [{ "website": "posthog.com", "jobTitles": ["founder"] }],
  "guardMode": "business",
  "verifyEmails": true
}
```

### Output Overview

Every result is one flat JSON object. Company rows have no `rowType`; person rows carry `rowType: "person"`; discovery rows carry `rowType: "discovered-person"`. Fields that don't resolve come back `null` or empty rather than missing, so the schema stays consistent. Download as JSON, CSV, Excel, or via the Apify API.

### Output Samples

A domain that publishes emails (Track A) - note the phones, harvested from the same pages:

```jsonc
{
  "query": null,
  "inputWebsite": "ahrefs.com",
  "companyName": null,
  "website": "https://ahrefs.com",
  "email": "support@ahrefs.com",
  "emails": ["support@ahrefs.com", "pr@ahrefs.com"],
  "emailCount": 2,
  "phone": "+16502530000",
  "phones": ["+16502530000"],
  "phoneCount": 1,
  "source": "site-scrape",
  "confidence": "high",
  "foundAt": "2026-07-23T05:20:41.000Z"
}
```

A person lookup (`people` input) - `rowType: "person"`:

```jsonc
{
  "rowType": "person",
  "fullName": "Patrick Collison",
  "companyDomain": "stripe.com",
  "email": "patrick.collison@stripe.com",
  "pattern": "first.last",
  "candidates": [
    { "email": "patrick.collison@stripe.com", "pattern": "first.last", "score": 40 },
    { "email": "patrick@stripe.com", "pattern": "first", "score": 15 },
    { "email": "pcollison@stripe.com", "pattern": "flast", "score": 14 }
    // …every standard corporate pattern, ranked
  ],
  "mxValid": true,
  "mailboxConfirmed": false,    // true = the mail server accepted this exact address and the domain is not catch-all
  "smtpStatus": "catch-all",   // confirmed · rejected (every likely address refused, email is null) · catch-all · unknown · skipped
  "emailProvider": "google-workspace",
  "source": "pattern",          // "site-match" = published on the company site · "site-pattern" = org's naming style learned from the site · "pattern" = frequency prior
  "confidence": "medium",       // high = mailbox confirmed or site-match · medium = MX-checked inference (or a first@/last@ confirmation) · low = no mail-capable result
  "foundAt": "2026-07-23T10:41:00.000Z"
}
```

A discovery result (`discoverPeople` input) - `rowType: "discovered-person"`, the person AND their verified email in one row:

```jsonc
{
  "rowType": "discovered-person",
  "fullName": "James Hawkins",
  "title": "Co-founder",
  "companyName": null,
  "companyDomain": "posthog.com",
  "location": null,
  "linkedinUrl": null,
  "discoverySource": "site-team-page",   // or "rocketreach-search" with a BYO key
  "discoveryQuery": "posthog.com",
  "email": "james.hawkins@posthog.com",
  "pattern": "first.last",
  "candidates": [ /* ranked as above */ ],
  "mxValid": true,
  "mailboxConfirmed": true,
  "smtpStatus": "confirmed",
  "emailProvider": "google-workspace",
  "source": "pattern",
  "confidence": "high",
  "foundAt": "2026-07-23T10:41:02.000Z"
}
```

With **`verifyEmails: true`**, company rows also carry per-address validation:

```jsonc
{
  // …the normal finder fields…
  "validEmails": ["jane.doe@acme.com"],
  "validEmailCount": 1,
  "bestValidEmail": "jane.doe@acme.com",
  "emailVerifications": [
    { "email": "jane.doe@acme.com", "syntaxValid": true, "domain": "acme.com", "mxFound": true,
      "isDisposable": false, "isRoleBased": false, "isFreeProvider": false, "verdict": "valid" },
    { "email": "info@acme.com", "syntaxValid": true, "domain": "acme.com", "mxFound": true,
      "isDisposable": false, "isRoleBased": true, "isFreeProvider": false, "verdict": "risky" }
  ]
}
```

`verdict` is `valid` (syntax + MX ok, not disposable), `risky` (role-based or free-provider), or `invalid` (bad syntax, no MX, or disposable).

### Key Output Fields

**Company rows**

- `website` - the resolved company website · `email`/`emails`/`emailCount` - ranked, de-duplicated
- `phone`/`phones`/`phoneCount` - phones from the same contact pages
- `takesPayments`/`paymentProcessors`/`stripeLiveKey` - with `qualifyByPayment`

**Person & discovered-person rows**

- `email` - best inferred address · `candidates` - every pattern, ranked with scores
- `mxValid` - domain accepts mail (the billing gate) · `emailProvider` - google-workspace, microsoft-365, …
- `title`/`linkedinUrl`/`location`/`discoverySource`/`discoveryQuery` - discovery rows only

**Provenance (all rows)**

- `source` - `site-scrape` / `hunter` / `site-match` / `site-pattern` / `pattern` / `none`
- `confidence` - `high` / `medium` / `low` · `foundAt` - ISO timestamp

### Pricing

Pay-per-event - you're charged for results, not runtime:

| Event | When it fires | Rate |
| --- | --- | --- |
| Person email found | A person (looked-up **or discovered**) resolves to a mail-capable business email. Never fires on dead domains, or when the mail server refuses every likely address. | $0.02 |
| Person email confirmed | On top of the above, when the mail server accepted that exact mailbox (`mailboxConfirmed: true`). From 16 October 2026. | $0.03 |
| Company enriched | One company row emitted (emails + phones + optional payment qualification). | $0.01 |
| Email verified | One address validated when `verifyEmails` is on. | $0.001 |
| Actor start | Once per run. | $0.005 |

**Discovery is free.** Finding the people costs nothing - you pay only when a discovered person resolves to a verified, mail-capable address. A 100-person discovery run where 60 resolve and 40 of those are confirmed by the mail server: $0.005 + 60 × $0.02 + 40 × $0.03 = **$2.41**, and the 40 misses cost nothing.

**What the price buys.** You pay per *result*, and only for one the mail server can stand behind: $0.02 for a mail-capable address, $0.05 when the server confirms the exact mailbox. The find step - discovering the people, inferring and ranking the addresses, the MX check, the site cross-check - is free. A budget name-and-domain lookup can quote a lower per-email number, but it starts from a list of names you already have and bills a second verifier actor on your own account to confirm anything. Here the discovery is done for you, the SMTP confirmation runs on our own server, and the ranked candidates, site cross-check, mail provider and provenance all come in the row you pay for.

### How this compares

| Capability | Resold B2B databases | Budget name→email finders | This actor |
| --- | --- | --- | --- |
| Finds the people for you | ✓ but from stored records | ✗ - you supply every name + domain | ✓ **Discovery, live, free** |
| Data resolved live, not months old | ✗ rows pulled long before you ran | ✓ | ✓ scraped at run time |
| Targets decision-makers by role | filter by title | ✗ | ✓ `targetPersona` presets |
| Verifies the address | varies | ✓ - often a second actor on your own account | ✓ MX + SMTP, **on our server** |
| Ranked candidate list + site cross-check | ✗ | ✗ (one address handed back) | ✓ full pattern list, scored, `site-match` |
| Mail provider + `source`/`confidence` per row | ✗ | rarely | ✓ every row |
| Phones + payment-stack qualification | ✗ | ✗ | ✓ included, free opt-in |
| API keys required | often (account / token) | varies | **none** (optional keys only add coverage) |
| Price per verified email | ~$0.0015-0.003 per stored row | from ~$0.005 + your own verifier bill | $0.02 MX / $0.05 server-confirmed, find step free |

The budget finders win on the raw per-email number - if you already hold a clean list of names and domains and only want the cheapest lookup, use one. This actor is for the harder job: you have a company or a role, not the names. It finds the people, resolves and ranks their addresses, confirms the mailbox on our own server, and returns the provenance - in one run, with the find step free and no second tool to pay for.

### Switching from another email or lead finder?

The most common complaints on other lead/email finders come down to paying for empty or junk results. Here's how each is handled here - every answer is a capability the actor already ships, not a promise:

| A complaint you may recognise | How this actor handles it |
| --- | --- |
| "Ran and consumed my credit with no output" · "all my credits gone for 2 emails" | Person results bill **only on a mail-capable hit** - misses cost $0, and so do people whose likely addresses the mail server all refuses. Company rows gate with `onlyWithEmails`. |
| "Not accurate" · "half the verified emails were undeliverable" | The company's mail server is asked whether the exact address exists before it's called confirmed; addresses are also cross-checked against the company's own published emails, with a confidence score - not a blind guess. |
| "Duplicate emails and random results" · "90% the same leads every run" | Inputs are normalised and de-duplicated to one billable row per company/person; every row is flat, self-contained, and carries its provenance. |
| "It just keeps running" · "one crawl returned 1,800 rows and drained my credits" | Hard caps you set: `maxItems`, `maxDiscoveredPeople`, `maxVerifiedEmailsPerCompany`, and per-query `maxPeople`. It never runs away. |
| "Can't get phone numbers" | Phones are harvested from the same contact pages and included on company rows at no extra charge. |
| "Unexpected / unknown enrichment charges" | Flat pay-per-event rates, listed in Pricing; person hits bill only when mail-capable, and the confirmed surcharge only when the mail server accepted the mailbox. Nothing hidden. |
| "A cheaper per-email finder, when I already have the names" | True - if you already hold every first name, last name, and domain, a budget lookup costs less per address. This actor is for when you *don't*: give it a domain and a role and it finds the people first, then resolves and confirms their emails - no name list to build, and no second verifier billed to your account. |
| "A B2B database / Apollo-style export gave me stale or bounced rows" | Those sell records pulled months before you ran. Here every address is resolved live from the company's current site and mail server, cross-checked against what it publishes now, with a confidence score - not a stored row someone pulled long ago. |

### Notes & limitations

- Site-engine Discovery finds people on companies that **publish a team/about/leadership page** (very common for agencies, startups, and professional services). Companies without one return no discovered people - honestly, rather than fabricating names. The open-web path (BYO RocketReach key) covers the rest.
- `locations` filters apply on the RocketReach path; public team pages don't publish per-person locations.
- Person emails are resolved by pattern ranking, MX check, site cross-checking and the SMTP mailbox check (`smtpCheck`, on by default), which also covers the per-address verification step (`verifyEmails` and verify-only `emailList`). A confirmed `first@` or `last@` address keeps confidence `medium`: the mailbox exists, but at a large company it may belong to someone else with that name. The check runs from our own mail-check server and gets honest answers from Gmail, Google Workspace, Microsoft 365 and most company mail servers. Catch-all domains accept every address, and some servers greylist or rate-limit checks; those come back `catchAll: true` or `accepted: null` and fall back to the DNS/MX verdict. `mailboxConfirmed: true` is the strong signal.
- Company email hit-rates depend on the company publishing an address; `source: "none"` rows tell you exactly which didn't.

### FAQ

**How is Discovery different from the `people` input?**
`people` needs a name you already know. `discoverPeople` finds the names: it scans the company's team/about/leadership pages (or searches the open web with your RocketReach key), extracts real people with their titles, and then runs each through exactly the same email-inference + verification pipeline. One run, one row per person, discovery free, billed only on verified hits.

**Can I pass a dataset instead of typing names?**
Yes. Set `peopleDatasetId` to an Apify dataset whose rows have a name and a company domain, or a `linkedin.com/in/` URL. Those rows are read after the People list and billed the same way.

**Can I paste LinkedIn profile URLs?**
Yes. Put `linkedin.com/in/…` links in `linkedinUrls`, in `people`, or in the paste box. The actor reads the public card (name, headline, employer) without a login, resolves the company domain, and runs the same person-email path. You are billed only when a mail-capable address is found. Company pages, jobs and sales-navigator links are ignored.

**Does Discovery spend my RocketReach credits?**
No. The actor calls only the RocketReach **search** endpoint, whose results include the person's professional email *domain* as a teaser. The actual address is resolved by this actor's own inference + MX verification. Per-contact unlock credits are never touched.

**Why did a discovery query return few (or no) people?**
The default engine reads what the company publishes. If there's no team/about page - or it's rendered entirely client-side - there's nothing trustworthy to extract, and the actor returns nothing rather than inventing names (strict validation drops nav labels, section headings, and marketing cards). Add a `rocketreachApiKey` for open-web coverage of those companies.

**What's the difference between passing a domain and a company name?**
A domain (Track A) is scraped exactly as given - no guessing. A company name (Track B) is resolved to a website first behind a name-match guard so the wrong company's domain is never attached. If you have domains, prefer them.

**Do I pay for companies or people where nothing was found?**
Person-level results (looked-up or discovered) are charged **only when mail-capable**. Company rows are charged when emitted, and `onlyWithEmails` is **on by default** - so no-email companies are dropped and not billed unless you set `onlyWithEmails: false` to keep them for coverage auditing.

**How are the emails ranked?**
Role/outreach addresses (`info@`, `sales@`, `contact@`, `hello@`) float to the top of company rows; junk (tracking pixels, asset filenames, placeholders, `no-reply@`) is stripped. Person rows rank every corporate pattern by real-world frequency, boosted by what the company's own site reveals.

**How does the person lookup work - is it scraped or guessed?**
Both, in the right order. All standard patterns are generated and ranked, the domain is MX-verified, the provider detected - and the company site is scanned: a published address returns as `site-match` (high confidence); published addresses revealing the org's naming style boost the matching pattern (`site-pattern`). Then the top candidates are asked of the mail server over SMTP, and the first one it accepts becomes the answer (`mailboxConfirmed: true`).

**Do I need a Hunter.io or RocketReach key?**
No. The free paths (site scrape, team-page discovery, pattern inference) work on their own. Keys are optional upgrades: Hunter.io adds a paid domain-search fallback; RocketReach upgrades discovery to open-web title/location search.

**How fast is it, and how many companies can it handle?**
Jobs run in parallel (tune `maxConcurrency`) and scale from a handful to thousands. Traffic routes through the actor's built-in residential proxy at no extra cost.

**What export formats are supported?**
Every run writes to an Apify dataset - JSON, CSV, Excel, XML, JSONL, or the Apify API - and wires into scheduling, webhooks, Google Sheets, Zapier, or Make.

### Support

- **Found a bug or need a new field?** Open a ticket on the **Issues** tab of this actor - it's the fastest way to reach me and I actively maintain this actor.
- **Email:** <muhamed.didovic@gmail.com>
- **Website:** [muhamed-didovic.github.io](https://muhamed-didovic.github.io/)

### Additional Services

Need something beyond the standard output? I build and maintain custom actors and data pipelines. Happy to help with:

- Deeper person-level contact discovery (titles, LinkedIn profiles, custom sources)
- Custom fields, filters, or export formats tailored to your workflow
- Private or dedicated actors for high-volume or compliance-sensitive use

Reach out via the Issues tab or email and describe what you need.

### Explore More Scrapers

Browse my other Apify actors - job boards, business directories, review sites, social platforms, and more - on my profile: [memo23 on Apify](https://apify.com/memo23).

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/email-finder`).

**Purpose:** find and verify business emails (plus phones on company rows) by company domain/name, by person (name + domain), or by discovery mode (job title / company / location → people → SMTP-confirmed or MX-checked emails); billed only on mail-capable hits.

**Minimal input:**

```json
{ "domains": ["stripe.com"], "maxItems": 50 }
```

Person mode: `{ "people": ["Patrick Collison, stripe.com"] }` · Discovery mode: `{ "discoverPeople": [{ "website": "posthog.com", "jobTitles": ["founder"] }] }` - modes can be mixed in one call.

**Output:** company rows carry `website, email, emails[], emailCount, phone, phones[], phoneCount, source, confidence, foundAt`. Person rows (`rowType: "person"`) carry `fullName, companyDomain, email, pattern, candidates[] {email, pattern, score, smtp}, mxValid, mailboxConfirmed, smtpStatus, emailProvider, source, confidence`. Discovery rows (`rowType: "discovered-person"`) carry the person fields plus `title, companyName, location, linkedinUrl, discoverySource, discoveryQuery`.

**Behaviors an agent should know:**

- `maxItems` caps companies processed (default 100000); `maxDiscoveredPeople` caps discovery output (default 200) - set both low to bound cost.
- Mix `domains`, `companyNames`, `people`, and `discoverPeople` in one run; `discoverPeople` accepts loose strings like `"CTO @ acme.com"`.
- Without `rocketreachApiKey`, discovery queries need a `company` or `website`; the actor never spends RocketReach unlock credits.
- `onlyWithEmails` defaults to **true** (no-email company rows are dropped and not billed); set it `false` to keep them. `verifyEmails: true` adds per-address validation; `smtpCheck` (default `true`) adds a real SMTP mailbox check with `mailboxConfirmed` per email; set it `false` for DNS-only.
- Billing: `company-enriched` $0.01 per company row (primary event); `person-email-found` $0.02 only when `mxValid` and an email resolved; `person-email-confirmed` +$0.03 when `mailboxConfirmed` (from 2026-10-16); email verified $0.001 each with `verifyEmails`; actor start $0.005. Discovery is free.

### ⚠️ Disclaimer

This actor discovers **publicly available** business contact information - the same email addresses, phone numbers, and team-page listings any visitor can see on a company's public website, plus (optionally, with your own API key) data from providers you hold an account with. It does not access private data, bypass authentication, or scrape gated content.

You are responsible for how you use the data you collect. Use it in compliance with applicable laws (including the GDPR, CAN-SPAM, CASL, and other data-protection and anti-spam regulations) and any contractual obligations you may have. Finding an email address does not constitute consent to contact it - ensure your outreach complies with the relevant marketing and messaging laws in your jurisdiction. Do not use this tool to facilitate spam, harassment, or any violation of privacy.

### SEO Keywords

email finder, bulk email finder, business email finder, company email lookup, domain email finder, email scraper, email extractor, find company emails, b2b email finder, lead generation tool, sales prospecting, contact email finder, email discovery, company contact scraper, domain to email, email hunter alternative, cold outreach emails, crm enrichment, bulk email lookup, email finder api, person email finder, find email by name and company, name to email, email pattern finder, email permutator, verified business email, mx verification, find employees by company, find people by job title, decision maker finder, team page scraper, apollo alternative, rocketreach alternative, discover leads by title, verified leads finder, email verification built in

# Actor input Schema

## `people` (type: `array`):

Person-level lookups. Each item is {"fullName": "Patrick Collison", "companyDomain": "stripe.com"} (plain strings like "Patrick Collison, stripe.com" also work). A LinkedIn profile URL (linkedin.com/in/…) is enough - the actor reads the public card for name and employer, then runs the same email path. For each person you get the most-likely business email with a ranked candidate list, live MX verification, the company's mail provider, and a confidence score. The company site is also scanned so a published address or the org's real naming pattern beats a blind guess. Billed per person with a mail-capable result.

## `peopleDatasetId` (type: `string`):

Apify dataset ID whose rows already have a person: firstName + lastName + domain, fullName + website, or a linkedin.com/in/ URL. Read after the People list. Company-only rows are skipped. Same billing as People: charged only when a mail-capable email is found.

## `linkedinUrls` (type: `array`):

Public linkedin.com/in/… profile links. Each one is read as a logged-out visitor (name, headline, employer), the company domain is resolved, then the same person-email path as the People field runs. No login or cookies. Billed only when a mail-capable business email is found. You can also paste these URLs into People or the list box.

## `emailList` (type: `array`):

Paste emails to validate - one per line. When this is filled the actor runs in <b>verify-only</b> mode: it validates each email and exits (no finding/discovery). Each email returns syntax, MX, disposable, role-based, free-provider flags and a <b>valid / risky / invalid</b> verdict. Billed per email via the <code>emails-verified</code> event.

## `discoverPeople` (type: `array`):

Discovery mode - don't know the people yet? Each query finds them AND resolves their emails in one run. Example: {"website": "stripe.com", "jobTitles": \["engineering", "marketing"]} scans the company's team/about/leadership pages, extracts every person matching your title filter, then runs each through the same verification pipeline as the People input (pattern inference, MX check, cross-checked against the site). No company? Leave website and company out and the query searches the open web by job title + location on its own, e.g. {"jobTitles": \["CTO"], "locations": \["Austin"]} - no key and no setup needed. A RocketReach API key below switches that open-web step to RocketReach instead. Fields per query: company, website, personName, jobTitles, locations, maxPeople. Plain strings work too ("CTO @ stripe.com"). Billed per person with a mail-capable result - discovery itself is free.

## `domains` (type: `array`):

Company websites or domains to find emails for (most accurate - the site is scraped directly). One per entry, e.g. stripe.com or https://stripe.com.

## `companyNames` (type: `array`):

Company / brand names to discover a website for, then find emails. Use when you don't have the domain. One per entry.

## `targetPersona` (type: `array`):

Pick the decision-maker roles to find at every domain / company you supply above - the actor auto-builds the people search for each, so a plain domain list becomes a decision-maker search. Combine several. Leave empty to use the Discover people queries instead.

## `defaultCountry` (type: `string`):

Bias the persona people-search toward one country/region (e.g. "United States", "United Kingdom"). Applied to every auto-built persona query. Leave empty for no location filter.

## `inputText` (type: `string`):

Alternative - paste a blob of domains, company names, or LinkedIn /in/ profile URLs separated by new lines, commas, or semicolons. Domains (containing a dot) are treated as websites; linkedin.com/in/… links become person lookups; everything else as a company name.

## `companies` (type: `array`):

Advanced: mix name + website + city/state per row for best disambiguation. Merged with the fields above.

## `guardMode` (type: `string`):

How strictly a discovered domain must match the company name. 'business' = name match (default, recommended). 'off' = accept any Clearbit match. 'residential' = property/real-estate mode.

## `onlyWithEmails` (type: `boolean`):

On by default: emit (and bill) only company rows where at least one email was found - so you pay only for successful hits, never for a no-email company. Set to false to also emit no-email rows (useful for auditing coverage); those company rows are billed when emitted.

## `verifyEmails` (type: `boolean`):

Validate every email found: checks syntax, looks up the domain's MX record (does it accept mail?), and flags disposable, role-based (info@, sales@) and free-provider (gmail, etc.) addresses. Adds an `emailVerifications` array plus `validEmails`, `validEmailCount` and `bestValidEmail` - so you get deliverable addresses, not just candidates. Includes a real SMTP mailbox check by default (toggle below), which adds `mailboxConfirmed` per email and `confirmedEmailCount` per company. Billed per email verified.

## `smtpCheck` (type: `boolean`):

Ask the company's mail server whether an address exists (EHLO → MAIL FROM → RCPT TO), with a random-address test to detect catch-all domains. Person rows: the top candidates (up to 4) are checked in order and the first one the server accepts becomes `email` with `mailboxConfirmed: true`; if the server refuses all of them, `email` is null and the row is not billed. Verified emails (`verifyEmails`, `emailList`) get an `smtp` object and `mailboxConfirmed`. Runs from our own mail-check server, because Apify blocks the SMTP port. When a server can't be reached or won't answer, results fall back to the DNS/MX check. A confirmed person email bills Person email confirmed on top of Person email found. Turn off for a faster DNS-only pass.

## `maxVerifiedEmailsPerCompany` (type: `integer`):

Optional ceiling on how many emails are verified per company. Verification is billed per email, and one company can legitimately expose dozens of addresses, so this is how you bound that spend. Any emails beyond the ceiling are still returned - just unverified and unbilled. Leave empty to verify everything found (the default behaviour).

## `qualifyByPayment` (type: `boolean`):

Scans each business's website - reusing the pages already fetched for email discovery, so no extra cost or time - for payment processors and e-commerce platforms (Stripe, Shopify, PayPal, Paddle, Lemon Squeezy, WooCommerce, Square, Chargebee and more). Adds `takesPayments` (is this a real paying business?), `paymentProcessors` (which stack), `stripeLiveKey` (the public key if exposed) and `paymentConfidence`. Turn raw contacts into monetization-qualified leads. No extra charge - included with each enriched company (billed under the existing Company enriched event).

## `hunterApiKey` (type: `string`):

Optional - supply your own Hunter.io key to enable a paid domain-search fallback when the site scrape finds no emails. Leave empty to use the free site-scrape path only.

## `rocketreachApiKey` (type: `string`):

Optional - an OFFICIAL RocketReach API key (rocketreach.co → Settings → API) upgrades Discovery mode to open-web people search: filter by job title, location, industry and employer without naming a company. The actor only calls the SEARCH endpoint and resolves emails with its own verification pipeline - it never triggers RocketReach per-contact lookups, so your unlock credits are not spent. Leave empty: open-web discovery already works without it, and a company or website still uses the team-page engine.

## `maxDiscoveredPeople` (type: `integer`):

Global cap on people processed by Discovery mode this run (across all discovery queries). Per-query caps can be set with each query's maxPeople field (default 25).

## `maxItems` (type: `integer`):

Hard cap on how many items this run processes. The budget is SHARED: companies are taken first, and person lookups get whatever is left over, so a long company list can leave no room for the `people` you also submitted (the run log warns when that happens). Free plans are capped at 25.

## `maxConcurrency` (type: `integer`):

How many companies to enrich in parallel.

## `proxy` (type: `object`):

Leave empty - the actor already routes all traffic through its own built-in residential proxy at no extra cost to you. Only set this if you want to use your own proxies.

## Actor input object example

```json
{
  "people": [
    {
      "fullName": "Patrick Collison",
      "companyDomain": "stripe.com"
    }
  ],
  "linkedinUrls": [
    "https://www.linkedin.com/in/satyanadella/"
  ],
  "emailList": [
    "jeff@amazon.com",
    "noreply@stripe.com",
    "not-a-real-address@example.invalid"
  ],
  "discoverPeople": [
    {
      "website": "stripe.com",
      "jobTitles": [
        "engineering"
      ]
    }
  ],
  "domains": [
    "stripe.com",
    "notion.so"
  ],
  "guardMode": "business",
  "onlyWithEmails": true,
  "verifyEmails": false,
  "smtpCheck": true,
  "qualifyByPayment": false,
  "maxDiscoveredPeople": 200,
  "maxItems": 100000,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `emails` (type: `string`):

One row per address with the deliverability verdict and the checks behind it: syntax, MX record, disposable, role-based and free-provider flags.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "people": [
        {
            "fullName": "Patrick Collison",
            "companyDomain": "stripe.com"
        }
    ],
    "linkedinUrls": [
        "https://www.linkedin.com/in/satyanadella/"
    ],
    "emailList": [
        "jeff@amazon.com",
        "noreply@stripe.com",
        "not-a-real-address@example.invalid"
    ],
    "discoverPeople": [
        {
            "website": "stripe.com",
            "jobTitles": [
                "engineering"
            ]
        }
    ],
    "domains": [
        "stripe.com",
        "notion.so"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/email-finder").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "people": [{
            "fullName": "Patrick Collison",
            "companyDomain": "stripe.com",
        }],
    "linkedinUrls": ["https://www.linkedin.com/in/satyanadella/"],
    "emailList": [
        "jeff@amazon.com",
        "noreply@stripe.com",
        "not-a-real-address@example.invalid",
    ],
    "discoverPeople": [{
            "website": "stripe.com",
            "jobTitles": ["engineering"],
        }],
    "domains": [
        "stripe.com",
        "notion.so",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/email-finder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "people": [
    {
      "fullName": "Patrick Collison",
      "companyDomain": "stripe.com"
    }
  ],
  "linkedinUrls": [
    "https://www.linkedin.com/in/satyanadella/"
  ],
  "emailList": [
    "jeff@amazon.com",
    "noreply@stripe.com",
    "not-a-real-address@example.invalid"
  ],
  "discoverPeople": [
    {
      "website": "stripe.com",
      "jobTitles": [
        "engineering"
      ]
    }
  ],
  "domains": [
    "stripe.com",
    "notion.so"
  ]
}' |
apify call memo23/email-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/email-finder"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/MSkWeQkEEwfEbafH7/builds/hWoIWCKMEkzuACTKn/openapi.json
