# Email Finder: Likely Business Addresses from Name and Domain (`santamaria-automations/email-finder-scraper`) Actor

Turn a name and a company domain into a ranked list of likely business email addresses. Checks the domain's MX records, flags free-mail and disposable domains and adds a catch-all heuristic. It does not confirm that a mailbox exists. Pay per person with candidates.

- **URL**: https://apify.com/santamaria-automations/email-finder-scraper.md
- **Developed by:** [NanoScrape](https://apify.com/santamaria-automations) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 email candidates founds

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: Likely Business Addresses from Name and Domain

Give the actor a first name, a last name and a company domain. For each person it builds the common address patterns, checks the domain's MX records, flags free-mail and disposable domains, adds a catch-all heuristic and returns the candidates ranked by how common each pattern is, with a `best_guess` on top.

**The result is a likely address, not a confirmed one.** The actor never contacts the recipient's mail server, so it cannot tell whether a mailbox exists or whether a message will be delivered. Use it to build a short list of candidates for outreach, and expect some of them to bounce.

### What you can do with it

- Turn a list of names and company domains into a ranked list of address candidates.
- Skip domains that cannot receive mail (no MX records) before you spend time on them.
- Spot free-mail and disposable domains in a lead list.
- Join results back to your list by `first_name`, `last_name` and `domain`.

### How a person is processed

1. The name is normalised: lower case, accents transliterated (`ü` becomes `ue`), everything except a-z removed.
2. The patterns are generated and ranked by how common they are in business addresses: `first.last`, `first`, `flast`, `f.last`, `firstlast`, `first_last`, `first.l`, `last.first`, `last`. The first one is the `best_guess`.
3. The domain's MX records are looked up (once per domain per run).
4. The domain is checked against a list of consumer mail providers and a bundled list of disposable-mail services. For those, and for domains without MX records, no candidates are generated.
5. A catch-all guess is made from the MX host name. It is a heuristic: hosts of known mail providers give `unknown`, hosts that look like shared web hosting give `likely`. It is never `no`, and `catch_all_method` is always `heuristic`.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `leads` | object\[] | People to look up. Each has `firstName`, `lastName` and `domain`. You can give `fullName` instead of the two name fields: `Jane Doe` and `Doe, Jane` are split into first and last name; with more than two words the first and the last word are used. The domain may be a bare domain, start with `www.` or be a full URL. A single object instead of a list also works. |

With no `leads`, four built-in sample people are used (placeholder names at apify.com, stripe.com, migros.ch and shopify.com). An explicit empty list checks nothing and charges nothing. The same person (same name and domain, ignoring case and `www.`) given twice produces one row.

```json
{
  "leads": [
    { "firstName": "Jane", "lastName": "Doe", "domain": "apify.com" },
    { "fullName": "John Smith", "domain": "https://www.stripe.com/" }
  ]
}
```

### Output

One row per input person, in input order.

| Field | Description |
|-------|-------------|
| `first_name`, `last_name` | The name as given (or split from `fullName`). |
| `domain` | The cleaned domain. |
| `status` | `ok`, `risky`, `no_mx`, `free_mail_domain`, `disposable_domain`, `dns_error` or `invalid_input` (see below). |
| `reason` | Short explanation for rows without candidates. |
| `best_guess` | The top-ranked candidate address. Left out when there are no candidates. |
| `best_guess_pattern` | Pattern name of `best_guess`. |
| `candidates` | Ordered list of `{email, pattern}`. Empty when none were generated. |
| `confidence` | `medium` when the domain has MX records and the best guess uses one of the four most common patterns, `low` otherwise. Never higher. |
| `mx_valid`, `mx_hosts`, `mx_provider` | MX result for the domain. |
| `free_mail`, `disposable` | Domain flags. |
| `catch_all`, `catch_all_method` | `likely` or `unknown`, and always `heuristic`. |
| `checked_at` | ISO 8601 timestamp. |

Status values:

- `ok`: candidates were generated and the domain has MX records.
- `risky`: as `ok`, but the catch-all heuristic says `likely`.
- `no_mx`: the domain has no MX records. No candidates.
- `free_mail_domain`, `disposable_domain`: no candidates are generated for these domains.
- `dns_error`: the MX lookup failed. No candidates.
- `invalid_input`: a first name, last name or the domain is missing or unusable. No candidates.

Sample row:

```json
{
  "first_name": "Jane",
  "last_name": "Doe",
  "domain": "apify.com",
  "status": "ok",
  "best_guess": "jane.doe@apify.com",
  "best_guess_pattern": "first.last",
  "candidates": [
    { "email": "jane.doe@apify.com", "pattern": "first.last" },
    { "email": "jane@apify.com", "pattern": "first" },
    { "email": "jdoe@apify.com", "pattern": "flast" }
  ],
  "confidence": "medium",
  "mx_valid": true,
  "mx_hosts": ["aspmx.l.google.com"],
  "mx_provider": "google_workspace",
  "free_mail": false,
  "disposable": false,
  "catch_all": "unknown",
  "catch_all_method": "heuristic",
  "checked_at": "2026-10-26T10:30:00Z"
}
```

(The candidate list is shortened here; real rows carry up to nine candidates.)

### Pricing

Pay per event:

| Event | When it is charged |
|-------|--------------------|
| `actor-start` | Once per run, after the first billable row has been delivered. A run that delivers no billable row is not charged. |
| `email-finder-result` | Per delivered row that has at least one candidate (`ok` or `risky`). |

Rows without candidates (`invalid_input`, `no_mx`, `free_mail_domain`, `disposable_domain`, `dns_error`) are delivered so you can see what happened, and are not charged. If you set a maximum total charge for the run, the actor stops when the next billable row would exceed it and the run ends with the status "Stopped at the max charge limit. N people checked."

### Use with AI agents (MCP)

Connect this actor to an MCP-compatible client such as Claude Desktop, Cursor or VS Code.

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://mcp.apify.com/?tools=santamaria-automations/email-finder-scraper",
        "--header",
        "Authorization: Bearer <YOUR_API_TOKEN>"
      ]
    }
  }
}
```

Example prompt: "Use email-finder-scraper to list the likely addresses for these 50 people and give me the rows with status ok."

### FAQ

**Is the address real?** Unknown. The actor does not check mailboxes. `best_guess` is the most common pattern for the name, and the rest of the list is there for the cases where the company uses another pattern.

**Why is there no candidate for a gmail.com address?** Consumer mail domains do not follow a company pattern, so no guess is made. The row is free.

**What does `catch_all: likely` mean?** The MX host looks like shared web hosting, where catch-all setups are common. It is a guess from the host name, not a test.

**Why did my row come back with `invalid_input`?** A first name, last name or domain was missing, or the domain is not a valid host name. These rows are not charged.

# Actor input Schema

## `leads` (type: `array`):

People to look up. Each entry has firstName, lastName and domain. Alternatively give fullName instead of firstName and lastName (split into first and last word). The domain may be a bare domain, start with www. or be a full URL. Duplicates (same name and domain) give one row.

## Actor input object example

```json
{
  "leads": [
    {
      "firstName": "Jane",
      "lastName": "Doe",
      "domain": "apify.com"
    },
    {
      "firstName": "John",
      "lastName": "Smith",
      "domain": "stripe.com"
    },
    {
      "firstName": "Maria",
      "lastName": "Muster",
      "domain": "migros.ch"
    }
  ]
}
```

# Actor output Schema

## `defaultDataset` (type: `string`):

One record per input person with the best guess, the ranked candidate list, MX result, domain flags and the catch-all heuristic

# 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 = {
    "leads": [
        {
            "firstName": "Jane",
            "lastName": "Doe",
            "domain": "apify.com"
        },
        {
            "firstName": "John",
            "lastName": "Smith",
            "domain": "stripe.com"
        },
        {
            "firstName": "Maria",
            "lastName": "Muster",
            "domain": "migros.ch"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("santamaria-automations/email-finder-scraper").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 = { "leads": [
        {
            "firstName": "Jane",
            "lastName": "Doe",
            "domain": "apify.com",
        },
        {
            "firstName": "John",
            "lastName": "Smith",
            "domain": "stripe.com",
        },
        {
            "firstName": "Maria",
            "lastName": "Muster",
            "domain": "migros.ch",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("santamaria-automations/email-finder-scraper").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 '{
  "leads": [
    {
      "firstName": "Jane",
      "lastName": "Doe",
      "domain": "apify.com"
    },
    {
      "firstName": "John",
      "lastName": "Smith",
      "domain": "stripe.com"
    },
    {
      "firstName": "Maria",
      "lastName": "Muster",
      "domain": "migros.ch"
    }
  ]
}' |
apify call santamaria-automations/email-finder-scraper --silent --output-dataset

```

## MCP server setup

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

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/FBMdzsEa5K8W87ItB/builds/n4HJQDp5vX9DOvMKr/openapi.json
