# Website Email Finder - Optional Email Verification (`leadproof/website-email-finder`) Actor

Find publicly listed email addresses on business websites, with source URLs. Scan a website list or CSV and optionally verify the extracted emails. Email verification is off by default.

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

## Pricing

from $5.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/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

## Website Email Finder - Optional Email Verification

Find email addresses published on company websites. Paste domains or supply a CSV file.
Each email comes with its source pages. Turn on mailbox verification only when you need it.

This is useful when you already have a list of companies from a CRM, directory, spreadsheet,
or another scraper and want to fill in their contact emails.

### How to use

1. Enter website URLs or domains in **Websites or domains**, or provide a **CSV file URL**.
2. Leave **Verify found email addresses** off for extraction only. This mode makes no
   verification requests and requires no verification-service credentials.
3. Optionally enable verification. The developer supplies the verification integration;
   customers do not need an API key.
4. Set website, page and email limits, then run the Actor.
5. Export the dataset as JSON or CSV. Read `SUMMARY` to check coverage and stop reasons.

Extraction only:

```json
{
  "websites": ["https://www.python.org/"],
  "verifyEmails": false,
  "maxWebsites": 10,
  "maxPagesPerWebsite": 5,
  "maxEmailsPerWebsite": 10
}
```

With verification, use the same input with `"verifyEmails": true`.

For CSV input, set `csvUrl` to a public or signed file URL and `websiteColumn` to the exact
column header (default: `website`). UTF-8 CSV, quoted fields, and a UTF-8 BOM are supported.
Files are limited to 2 MB and 1,000 nonempty website entries. CSV and direct inputs are combined.

### Output

One row per **website/email pair**. Repeated occurrences on the same site are combined into
one row with all observed source pages. A domain supplied more than once is scanned once.
The same email published by different domains may produce one row for each domain.
The verifier caches repeated addresses during a run.

If a site yields no email, an uncharged diagnostic row is returned with `email: null`.
`no_email_found` means the fetched pages had no usable email; it is not proof that the entire
website has no email. `website_failed` means no content page could be read.

| Field | Meaning |
| --- | --- |
| `website`, `domain` | Input website and normalized domain |
| `email` | Published email, or null on diagnostics |
| `sourceUrls`, `methods` | Source pages and extraction methods |
| `verificationStatus` | `not_requested`, `ok`, `catch_all`, `unknown`, `invalid`, `disposable`, `error`, `not_applicable` |
| `isDeliverable` | True for provider `ok`, false for `invalid`/`disposable`, null otherwise |
| `verificationReason` | Sanitized reason when verification fails |
| `scanStatus` | `succeeded`, `partial`, or `failed` |
| `resultType` | `email`, `no_email_found`, or `website_failed` |
| `pagesAttempted`, `pagesSucceeded` | Content-page coverage; excludes robots.txt and redirects |
| `errors` | URLs and failure categories for unreadable/disallowed pages |
| `crawlLimited` | More pages or addresses remained beyond the configured limits |
| `billingEvent` | `email-found`, `email-checked`, or null on diagnostics |
| `scrapedAt` | UTC timestamp |

`ok` is a mailbox verification result at the time of checking, not a guarantee of future
delivery. Catch-all and unknown results never set `isDeliverable` to true. In extraction-only
mode, every returned email has `verificationStatus: "not_requested"` and `isDeliverable: null`.

### What it scans

- The starting page and discovered contact, about, team, staff, and international equivalents.
- Common contact/about paths as fallbacks, within the same page limit.
- Visible text, `mailto:` links, JSON-LD email fields, and Cloudflare's encoded public emails.
- HTTP(S) sites on standard ports, with TLS validation, response limits, and bounded redirects.
- `robots.txt` rules, including crawl delays up to 10 seconds. Longer delays are reported as
  unsupported instead of ignored. An unavailable robots file fails the site conservatively.

The Actor does not guess mailbox names, log in, execute JavaScript, solve challenges, OCR
images, or crawl social networks. JS-only email displays and protected pages can be missed.
Navigation stays on the input hostname, allowing its www/non-www equivalent. Redirects to
other domains are reported. Free-provider addresses such as Gmail are included unless
`sameDomainOnly` is enabled. A published address can belong to a vendor or partner; the source
page is evidence of publication, not proof of ownership.

### Pricing behavior

Extraction costs **$5 per 1,000 returned email rows**. Extraction with a completed verification
costs **$10 per 1,000 rows**, including extraction. The two events are mutually exclusive:

- `email-found`: extracted email, no completed verification. Provider-error results also use
  this event, with `verificationStatus: "error"`.
- `email-checked`: extracted email plus a completed provider check, including catch-all,
  unknown, invalid and disposable results. **This replaces email-found; it is not added to it.**

Diagnostic rows have no custom event charge. Actor start costs $0.00005 per GB of allocated
memory, with a one-event minimum. Platform usage is included. No third-party Actor is run on
the customer's account. Missing verification
configuration or credits fails before crawling when verification is enabled. Three provider
errors stop the run with partial results retained. Spending limits are checked before each
provider request and output charge; a batch of at most `concurrency` websites may already have
been scanned when the budget is reached.

### Summary and recovery

`SUMMARY` reports input deduplication, websites omitted by the limit, processed/readable/failed
sites, sites without emails, returned email rows, checked rows, provider errors, and stop reason.
If every website fails, the Actor fails with diagnostic rows saved instead of claiming a
successful empty search. `CHECKPOINT` records completed sites and an input fingerprint.
After migration or restart, already emitted website/email pairs are skipped. Checkpoint and
dataset writes are not transactional: a crash between a platform charge and its durable write
cannot provide an exactly-once billing guarantee. Start a fresh run when changing input.

### Development

```sh
python -m pip install -r requirements.txt
python -m unittest discover -s test -v
python -m src
```

For local input, create `storage/key_value_stores/default/INPUT.json`.
Only verification mode needs the developer's `MV_API_KEY` secret environment variable.
Do not put API keys in input examples, source files, logs, or result datasets.

Use published business contacts responsibly and comply with applicable rules and site terms.

Related LeadProof tools:
[Google Maps Scraper](https://apify.com/leadproof/google-maps-scraper),
[Bulk Email Verifier](https://apify.com/leadproof/bulk-email-verifier),
[Google Maps Verified Leads](https://apify.com/leadproof/google-maps-verified-leads),
and [LeadProof](https://leadproof.co).

# Actor input Schema

## `websites` (type: `array`):

Company websites to scan. Duplicate domains are scanned once. Supply this list, a CSV URL, or both.

## `csvUrl` (type: `string`):

Public or signed HTTP(S) URL of a UTF-8 CSV, up to 2 MB and 1000 websites. The file must contain the website column selected below.

## `websiteColumn` (type: `string`):

Exact CSV header containing the website URL or domain.

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

Off by default: collect published emails only, with no verification requests. Turn on to check each found address. Checked addresses use the email-checked event instead of email-found. Catch-all and unknown do not confirm a working mailbox.

## `sameDomainOnly` (type: `boolean`):

When enabled, omit Gmail and other external-domain addresses. Off includes all usable published emails; a source page is always attached.

## `maxWebsites` (type: `integer`):

Maximum unique domains processed after deduplication. Additional domains are reported as omitted.

## `maxPagesPerWebsite` (type: `integer`):

Limit on content pages, including the starting page. robots.txt is an additional request; redirects are bounded.

## `maxEmailsPerWebsite` (type: `integer`):

Maximum distinct email addresses returned per website. Own-domain addresses are preferred.

## `concurrency` (type: `integer`):

Number of websites scanned at once. Verification and billing are processed serially to respect the spending limit.

## `requestTimeoutSeconds` (type: `integer`):

Maximum time per web request. Sites that fail are returned with diagnostics.

## Actor input object example

```json
{
  "websites": [
    "https://www.python.org/"
  ],
  "websiteColumn": "website",
  "verifyEmails": false,
  "sameDomainOnly": false,
  "maxWebsites": 100,
  "maxPagesPerWebsite": 5,
  "maxEmailsPerWebsite": 10,
  "concurrency": 5,
  "requestTimeoutSeconds": 20
}
```

# Actor output Schema

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

Full results with evidence and optional verification.

## `csv` (type: `string`):

Email and website diagnostic rows for spreadsheet import.

## `summary` (type: `string`):

Website coverage, returned email count, verification outcomes and stop reason.

## `checkpoint` (type: `string`):

Input fingerprint, completed website URLs and counters used for migration recovery.

# 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 = {
    "websites": [
        "https://www.python.org/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("leadproof/website-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 = { "websites": ["https://www.python.org/"] }

# Run the Actor and wait for it to finish
run = client.actor("leadproof/website-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 '{
  "websites": [
    "https://www.python.org/"
  ]
}' |
apify call leadproof/website-email-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,leadproof/website-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/Y5Cr4ZE8HIpiMNXUF/builds/bqwZiJMfPZJYCABHR/openapi.json
