# Email Finder & Verifier — Person & Company Emails + Phones (`memo23/email-finder`) Actor

Find AND verify business emails in bulk — from company names, domains, or a person's name + company domain (pattern-inferred, MX-verified, cross-checked against the site). Scrapes emails + phones, ranks addresses, validates each (syntax, MX, disposable/role/free). CSV/JSON/API.

- **URL**: https://apify.com/memo23/email-finder.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Lead generation, AI, Agents
- **Stats:** 48 total users, 37 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Email Finder & Verifier — Discover People, Emails & Phones

<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>

The email actor that **verifies what it finds — and now finds WHO to email in the first place**. Give it companies and it scrapes their sites for emails and phones. Give it a person's name + domain and it infers their business address — pattern-ranked, MX-verified, cross-checked against the company's own site. And with the new **Discovery mode**, give it just a job title, company, or location and it finds the people first, then resolves each one to a verified email. Discovery + verification in one SKU, one run, one flat row per result.

| Input | Row(s) emitted |
| --- | --- |
| Domain or company name | One company row — ranked emails + phones, provenance, confidence |
| Person (`fullName` + `companyDomain`) | One `person` row — best inferred email, ranked candidates, MX + provider |
| **Discovery query (title / company / location)** | One `discovered-person` row per person found — name, title, LinkedIn + verified email |

> Pure HTTP. No browser, no login, no cookies — and no 5,000-row dumps of unverified guesses.

### Why Use This Scraper?

- **You pay for verified hits, not volume.** Bulk email dumpers charge for every row they emit, verified or not. Here, a person-level result is billed **only when the resolved address is mail-capable** (live MX check) — a dump of dead addresses costs them money and you nothing.
- **Discovery mode — start from zero contacts.** Don't know who works there? `{"website": "stripe.com", "jobTitles": ["engineering"]}` scans the company's team/about/leadership pages, extracts real people (JSON-LD, LinkedIn cards, team-card markup — with strict name validation so section headings never become "leads"), and pushes each through the same verification pipeline. Add your own RocketReach API key to search the open web by title + location without naming a company — the actor only uses the search teaser, **never spends your per-contact unlock credits**.
- **Verification is built into every path.** Person and discovery rows are MX-verified and cross-checked against addresses actually published on the company site — a published address or the org's real naming style always beats a blind guess. Company rows can opt into full per-address validation (`verifyEmails`): syntax, MX, disposable / role / free-provider flags.
- **Ranked candidates, not a single guess.** Every person-level row carries the full ranked pattern list (`first.last@`, `flast@`, `first@`, …) with scores, the company's mail provider (Google Workspace, Microsoft 365, …), and a `source`/`confidence` pair telling you exactly how the answer was derived.
- **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.
- **Optional Hunter.io fallback.** Supply your own key to add a paid domain-search fallback when the on-site scrape comes up empty.

### Overview

Email Finder is a **bulk business-email discovery and verification** tool with three modes you can mix freely in one run:

- **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.
- **People** — you know the person; `{fullName, companyDomain}` returns their most-likely business email with ranked candidates, MX verification, and site cross-checking.
- **Discovery** *(new)* — you know only the title/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 exactly like the People mode. Discovery itself is **free** — you're billed per verified email found, same event, same price.

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" }` or `"Patrick Collison, stripe.com"` strings.
- **`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"`.

### 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, and the result cross-checked against emails actually published on the site (a published address = `site-match`, high confidence). 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). |
| `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 | Emit only rows where an email was found — pay only for hits. |
| `verifyEmails` | boolean | Validate every found company email (syntax + MX + disposable/role/free flags); adds `emailVerifications`, `validEmails`, `bestValidEmail`. Billed per email verified. |
| `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",
  "onlyWithEmails": false
}
```

### 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,
  "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 = site-match · medium = MX-verified inference · low = domain accepts no mail
  "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,
  "emailProvider": "google-workspace",
  "source": "pattern",
  "confidence": "medium",
  "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. | $0.02 |
| 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: $0.005 + 60 × $0.02 = **$1.21**, and the 40 misses cost nothing.

### What makes this different from bulk email dumps

| Capability | Bulk dump actors | Pattern-only guessers | This actor |
| --- | --- | --- | --- |
| Billing unit | Every emitted row, verified or not | Per pattern test | **Only mail-capable results** |
| MX verification | ✗ | Sometimes | ✓ live, per row |
| Cross-check vs the company's own site | ✗ | ✗ | ✓ (`site-match` / `site-pattern`) |
| Ranked candidate list with scores | ✗ | ✗ | ✓ |
| Finds the people, not just the addresses | ✗ (bring your own list) | ✗ | ✓ Discovery mode, free |
| Phones | ✗ | ✗ | ✓ included |
| Payment-stack qualification | ✗ | ✗ | ✓ free opt-in |

A 5,000-row dump you have to verify elsewhere isn't cheaper — it's the same work moved to your desk, plus a second tool's bill. This actor ships the verification, the ranking, and the provenance in the row you pay for.

### 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-level verification is DNS/MX-based (does the domain accept mail?) plus site cross-checking — it does not do live SMTP mailbox probing, which mail servers widely block and penalize.
- 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.

**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 — set `onlyWithEmails: true` to drop and not pay for no-hit companies.

**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`).

**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 → MX-verified 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}, mxValid, 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.
- Set `onlyWithEmails: true` to drop and not pay for no-hit company rows; `verifyEmails: true` adds per-address validation.
- Billing: `company-enriched` $0.01 per company row (primary event); `person-email-found` $0.02 only when `mxValid` and an email resolved; 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). 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.

## `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). Add a RocketReach API key below to also search the open web by job title + location WITHOUT naming a company, e.g. {"jobTitles": \["CTO"], "locations": \["Austin"]}. 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.

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

Alternative — paste a blob of domains OR company names separated by new lines, commas, or semicolons. Domains (containing a dot) are treated as websites; 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`):

Emit only rows where at least one email was found — so you pay only for successful hits.

## `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. (Reliable DNS/list-based validation; does not do live SMTP mailbox probing.) Billed per email verified.

## `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 to use the built-in team-page discovery 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"
    }
  ],
  "discoverPeople": [
    {
      "website": "stripe.com",
      "jobTitles": [
        "engineering"
      ]
    }
  ],
  "domains": [
    "stripe.com",
    "notion.so"
  ],
  "guardMode": "business",
  "onlyWithEmails": false,
  "verifyEmails": false,
  "qualifyByPayment": false,
  "maxDiscoveredPeople": 200,
  "maxItems": 100000,
  "maxConcurrency": 10
}
```

# 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"
        }
    ],
    "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",
        }],
    "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("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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"
    }
  ],
  "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": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=memo23/email-finder",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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