# Decision-Maker Email Finder (`cancap/email-finder-actor`) Actor

Find real, named contact emails for any company straight from their own website — not generic info@ addresses. Crawls team/about pages, generates verified patterns, checks MX validity, and scores confidence transparently.

- **URL**: https://apify.com/cancap/email-finder-actor.md
- **Developed by:** [CANCAP](https://apify.com/cancap) (community)
- **Categories:** Lead generation
- **Stats:** 3 total users, 2 monthly users, 79.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 email 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Decision-Maker Email Finder

Finds real, named contact emails for a company by crawling **only that company's own public website** — no LinkedIn, no login-walled platforms, no paid third-party email-verification APIs.

### What it does

1. Takes a list of companies (domain + optional name and target job titles).
2. Crawls a small set of likely pages on the company's **own** website: homepage, `/about`, `/team`, `/contact`, etc.
3. Extracts:
   - Emails already on the page (mailto: links, visible text)
   - Names + job titles from team/about sections (best-effort heuristic — real-world HTML varies)
4. If no email is found directly but a named person is, generates likely email patterns (`first.last@domain`, `flast@domain`, etc.) — pure logic, no API.
5. Optionally verifies candidate emails for free:
   - MX lookup (DNS) — confirms the domain can receive mail at all
   - SMTP handshake probe (`RCPT TO` check, no message ever sent) — estimates deliverability
6. Falls back to generic addresses (`info@`, `contact@`) only if nothing better was found, or if explicitly requested.
7. Outputs one row per candidate email with a transparent 0–100 confidence score.

### Why "zero cost to the builder"

Every step uses either:
- The **user's own Apify compute** (billed to whoever runs the actor, via Apify's usual platform-usage billing), or
- A free, no-account-needed mechanism (DNS MX lookup, raw SMTP socket connection, regex/HTML parsing).

No step calls a paid API (no Hunter.io, NeverBounce, ZeroBounce, Clearbit, etc.) and no step requires you, the actor's author, to hold any third-party API key or pay for any external service.

### Changelog

**v0.1.1 — fixes from real-world testing on Windows**

A user ran this on a real Windows machine against `apify.com` and caught two real bugs:

1. **`mxValid` incorrectly returned `false` for a domain with valid MX records.** Root cause: DNS resolution can intermittently fail on some Windows/VPN/corporate network setups (no error was being surfaced, just a silent failure treated as "no MX records"). Fixed by adding a retry and explicit error logging in `mxCheck.js`, so failures are now diagnosable in the run log instead of silently miscategorized.
2. **A project showcase block was misidentified as a person.** Apify's homepage features community projects (e.g. a tool with "Founder" in its description), and the original heuristic loosely matched any short Title-Case heading near the word "founder" as a person + job title — producing garbage like `personName: "Hlidac Shopu"` with a run-on sentence as the job title. Fixed by making `extractPeople()` in `pageExtract.js` require the "title" to come from a genuinely separate sibling element, be short (≤60 chars), and not contain sentence punctuation — rejecting description blurbs while still matching real `<h3>Jane Doe</h3><p>CEO</p>`-style team cards.

Both fixes were verified with targeted unit tests (one positive case, one reproducing the exact false-positive pattern) plus a full end-to-end re-run. See git history / ask the author for the before/after test output if you want to verify yourself.

3. **`ECONNREFUSED` on MX lookup, specifically on a Windows machine using a mobile hotspot.** The OS-level resolver (Windows `nslookup`) worked fine for MX queries on that same network, but Node's built-in DNS resolver (`c-ares`) failed to even reach a DNS server. This is a known class of issue where Node's resolver path behaves differently from the OS's own resolver on certain Windows network setups (VPNs, hotspots, some routers). Fixed by explicitly setting public DNS servers (`dns.setServers([...])` — Google + Cloudflare) instead of relying on whatever the OS hands Node by default. This makes MX lookups more robust across the wide range of networks real users will run this actor from, not just the one we debugged.

### Honest limitations (please read before publishing)

- **SMTP verification is a signal, not a guarantee.** Gmail, Microsoft 365, and many providers accept all `RCPT TO` requests at the SMTP layer and only bounce later — so `smtpCheck: "accepted"` means "didn't reject it immediately," not "100% confirmed deliverable." This is documented in the output schema description on purpose, so users aren't misled.
- **Some networks block outbound port 25 entirely.** When that happens, `smtpCheck` returns `"unknown"` rather than a false negative. This is expected and handled gracefully — it will NOT crash the run.
- **Name/title extraction is heuristic.** Team page HTML varies enormously across the web. It will work well on many sites and miss on others. This is a best-effort layer, not a guarantee — the confidence score reflects that (pattern-generated emails always score lower than found-on-page emails).
- **This does not scrape LinkedIn or any login-walled site.** That's intentional — it keeps the actor legally and technically simple, and avoids account-ban risk entirely.

### What I tested before handing this to you, and what I could NOT fully test

I ran this in a sandboxed environment with restricted outbound networking (only a fixed domain allowlist), so I was able to fully verify:
- ✅ All JSON config files parse correctly (`actor.json`, `input_schema.json`, `dataset_schema.json`, `package.json`)
- ✅ `npm install` completes cleanly with no errors
- ✅ Email pattern generation logic (unit tested directly)
- ✅ Confidence scoring logic (unit tested directly)
- ✅ MX/DNS lookups against a real domain (`apify.com` — returned real Google MX records)
- ✅ Page extraction (emails + people) against sample HTML, including correctly rejecting an image filename like `logo@2x.png`
- ✅ Full end-to-end run via `npm start` against a real live website (`github.com`), producing clean, correctly-shaped output rows with sensible fallback behavior

What I could **not** fully verify from my sandbox:
- ⚠️ **SMTP probing (`smtpProbe.js`) against a real mail server.** My sandbox's network blocks outbound port 25 entirely (confirmed directly — connections to `aspmx.l.google.com:25` silently time out with no error, even via raw `curl`). The code handles this failure gracefully and returns `"unknown"` rather than crashing, which I confirmed. But I could not confirm it returns `"accepted"`/`"rejected"` correctly against a live mail server, because my network can't reach port 25 at all.

**When you run this on real Apify infrastructure, please specifically check:**
1. Run with `verifyEmails: true` against a domain where you know a real email exists, and confirm `smtpCheck` returns `"accepted"` (not always `"unknown"`).
2. Run against a domain you know does NOT have a particular address, and confirm `smtpCheck` returns `"rejected"` rather than always defaulting to `"unknown"`.
3. If `smtpCheck` is *always* `"unknown"` no matter what, that means Apify's network also restricts outbound port 25 for your account/plan — in that case, tell me and I'll adjust the actor to make SMTP verification optional-with-a-clear-warning rather than a default-on feature.

### Local testing

```bash
npm install
## Edit storage/key_value_stores/default/INPUT.json with your test input (see input_schema.json for shape)
APIFY_LOCAL_STORAGE_DIR=./storage npm start
## Check storage/datasets/default/*.json for output
````

### Deploying to Apify

```bash
npm install -g apify-cli
apify login
apify push
```

Then open the actor in the Apify Console, set your input, click **Start**, and check the **Output** tab.

### Pricing suggestion

Set this up as **Pay Per Event (PPE)** with an event like `email-found` (charged only when `email` is non-null and `source` is not `"none"`). This way users only pay for results, which builds trust faster than charging per page crawled.

## Actor input object example

```json
{}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("cancap/email-finder-actor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("cancap/email-finder-actor").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 '{}' |
apify call cancap/email-finder-actor --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Decision-Maker Email Finder",
        "description": "Find real, named contact emails for any company straight from their own website — not generic info@ addresses. Crawls team/about pages, generates verified patterns, checks MX validity, and scores confidence transparently.",
        "version": "0.0",
        "x-build-id": "X4FmgjE2ZrMUTcRLU"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/cancap~email-finder-actor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-cancap-email-finder-actor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/cancap~email-finder-actor/runs": {
            "post": {
                "operationId": "runs-sync-cancap-email-finder-actor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/cancap~email-finder-actor/run-sync": {
            "post": {
                "operationId": "run-sync-cancap-email-finder-actor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {}
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
