# Website Email Extractor (`automation-lab/website-email-extractor`) Actor

Crawl public websites and export normalized displayed email addresses with exact source-page provenance, discovery context, and timestamps.

- **URL**: https://apify.com/automation-lab/website-email-extractor.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Website Email Extractor

Crawl public websites and export displayed email addresses with the exact page where each address was found. This **website email extractor** turns supplied URLs or domains into structured, deduplicated email records for lead enrichment, contact research, spreadsheet exports, and scheduled data refreshes.

The Actor uses lightweight HTTP requests rather than a browser. It checks each supplied page and follows bounded, same-site contact-related links such as contact, about, team, support, legal, and impressum pages.

### What does Website Email Extractor do?

Website Email Extractor:

- accepts one or many public website URLs or bare domains;
- finds email addresses in `mailto:` links and displayed page text;
- recognizes common `[at]` and `[dot]` obfuscation;
- follows high-signal contact links within configurable depth and page limits;
- normalizes email addresses and domains;
- records the exact source page and original start URL;
- deduplicates repeated email-and-source-page pairs;
- exports typed JSON, CSV, Excel, XML, RSS, or JSONL through Apify datasets.

It does not guess addresses, generate email patterns, verify mailboxes, or access pages behind logins. Every result is tied to publicly displayed source evidence.

### Who is this website email scraper for?

#### Lead-generation teams

Refresh public business contact lists while preserving source-page provenance for review and CRM updates.

#### Sales and partnerships teams

Collect displayed sales, support, press, partnership, or general contact addresses from a bounded list of target websites.

#### Researchers and analysts

Build reproducible datasets of website email addresses with crawl timestamps and discovery context.

#### Data engineers

Feed normalized website email records into spreadsheets, databases, enrichment pipelines, webhooks, or scheduled Apify workflows.

#### Agencies

Process client-supplied domain lists without manually opening each contact page.

### Why use this online email extractor?

Many email extraction tools return only an address. This Actor also returns:

- the normalized email domain;
- the website domain being researched;
- the exact public source URL;
- the supplied start URL;
- whether the address came from visible text, a mail link, or obfuscated text;
- the source page title;
- the extraction timestamp.

That provenance makes results easier to audit, deduplicate, refresh, and route to downstream systems.

### What data can I extract?

| Field | Type | Description |
| --- | --- | --- |
| `email` | string | Normalized displayed email address. |
| `domain` | string | Domain portion of the email address. |
| `websiteDomain` | string | Normalized domain of the supplied website. |
| `sourceUrl` | URL | Exact public page where the address appeared. |
| `startUrl` | URL | Supplied website URL that led to the result. |
| `discoveryContext` | string | `mailto-link`, `visible-text`, or `obfuscated-text`. |
| `pageTitle` | string or null | HTML title of the source page when available. |
| `linkText` | string or null | Text of the email link when applicable. |
| `foundAt` | ISO timestamp | Time when the address was extracted. |

A repeated email on two different pages creates two records because each source page is independently useful provenance. An email repeated on the same page is saved once.

### How to extract email addresses from websites

1. Open Website Email Extractor in Apify Console.
2. Add one or more public website URLs to **Website URLs**.
3. Choose the maximum pages and contact-link depth for each website.
4. Keep direct requests enabled, or select an Apify Proxy configuration if your targets require one.
5. Click **Start**.
6. Preview results in the **Website emails** dataset view.
7. Export the dataset or connect it to another service.

Start with a small domain batch and `maxPagesPerWebsite` between 3 and 10. Increase limits only when the target sites need deeper contact-page discovery.

### Input parameters

| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | yes | W3C and GNU contact pages | Public URLs or domains to crawl. |
| `maxItems` | no | `20` | Maximum unique email-and-source-page records, from 1 to 100,000. |
| `maxPagesPerWebsite` | no | `10` | Maximum pages fetched per supplied website, from 1 to 100. |
| `maxDepth` | no | `2` | Contact-link levels to follow, from 0 to 3. |
| `includeSubdomains` | no | `false` | Whether high-signal links on subdomains may be followed. |
| `proxyConfiguration` | no | direct requests | Optional Apify Proxy settings. |

#### Example input

```json
{
  "startUrls": [
    { "url": "https://www.w3.org/Consortium/contact" },
    { "url": "https://www.gnu.org/contact/" }
  ],
  "maxItems": 50,
  "maxPagesPerWebsite": 6,
  "maxDepth": 1,
  "includeSubdomains": false,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

Bare domains such as `gnu.org` are normalized to HTTPS URLs. Non-HTTP protocols and local or private-network targets are rejected.

### Output example

A real output record has this shape:

```json
{
  "email": "team-china-contact@w3.org",
  "domain": "w3.org",
  "websiteDomain": "w3.org",
  "sourceUrl": "https://www.w3.org/contact",
  "startUrl": "https://www.w3.org/Consortium/contact",
  "discoveryContext": "visible-text",
  "pageTitle": "Contact | W3C",
  "linkText": null,
  "foundAt": "2026-08-13T14:06:51.837Z"
}
```

The default dataset contains the primary result type, so Apify integrations and dataset export endpoints work without a custom dataset name.

### How much does it cost to extract website email addresses?

Website Email Extractor uses pay-per-event pricing:

- a one-time **$0.004 run-start fee**;
- **$0.00304 per extracted email record** on the BRONZE tier;
- lower per-record rates on higher Apify tiers.

BRONZE examples use the same simple calculation:

| Result count | Charge calculation |
| ---: | --- |
| 1 | one run-start event plus 1 email event |
| 10 | one run-start event plus 10 email events |
| 100 | one run-start event plus 100 email events |
| 1,000 | one run-start event plus 1,000 email events |

Only unique records successfully saved to the dataset trigger the email event. Failed pages, duplicate records, and pages with no displayed email do not create per-email charges. Apify applies the tier associated with your account; proxy usage or platform compute can vary separately.

### Bounded crawling behavior

The crawler starts from each supplied URL. It follows links only when all of these are true:

1. the destination is on the same website, unless subdomains are explicitly enabled;
2. the path, query, or link text looks contact-related;
3. the configured depth has not been exceeded;
4. the per-website page limit has not been reached.

This is intentionally not a general whole-site crawler. The bounded strategy controls runtime, transfer, and accidental crawl expansion while prioritizing pages likely to display contact details.

### Email normalization and discovery context

Addresses are lowercased and checked for a valid domain structure. Obvious reserved test domains are discarded.

The `discoveryContext` field explains how each address appeared:

- `mailto-link` — extracted from an email link;
- `visible-text` — displayed directly in page text;
- `obfuscated-text` — displayed with supported `[at]`, `(at)`, `[dot]`, or `(dot)` notation.

JavaScript-generated addresses that never appear in returned HTML are outside the lightweight HTTP route.

### Export to CSV, Excel, or JSON

After a run, open the dataset and choose an export format. You can also use dataset API endpoints, for example:

```text
https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv&clean=true
```

Common formats include JSON, JSONL, CSV, Excel, XML, and RSS. Use `clean=true` to omit storage metadata where supported.

### Lead enrichment workflow

A practical recurring workflow is:

1. export domains from a CRM or prospecting source;
2. submit the domain list in `startUrls`;
3. run Website Email Extractor on a schedule;
4. join records by `websiteDomain`;
5. preserve `sourceUrl` and `foundAt` for auditability;
6. compare the latest dataset with the previous export before updating the CRM.

The Actor reports what a website displays. It does not determine mailbox ownership, role, deliverability, consent, or whether an address is suitable for outreach.

### Scheduling and monitoring

Create an Apify Task with your input, then attach an Apify Schedule for recurring refreshes. Webhooks can notify another system when the run succeeds or fails.

When comparing scheduled outputs, use `email`, `websiteDomain`, and `sourceUrl` as stable business keys. Treat `foundAt` as observation time rather than a change indicator.

### API usage with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~website-email-extractor/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{"url":"https://www.w3.org/Consortium/contact"}],
    "maxItems": 20,
    "maxPagesPerWebsite": 5,
    "maxDepth": 1
  }'
```

For production integrations, prefer an `Authorization: Bearer` header instead of putting a token in a logged URL.

### API usage with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/website-email-extractor').call({
  startUrls: [{ url: 'https://www.w3.org/Consortium/contact' }],
  maxItems: 20,
  maxPagesPerWebsite: 5,
  maxDepth: 1,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### API usage with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/website-email-extractor').call(run_input={
    'startUrls': [{'url': 'https://www.gnu.org/contact/'}],
    'maxItems': 20,
    'maxPagesPerWebsite': 5,
    'maxDepth': 1,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use Website Email Extractor with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/website-email-extractor"
```

#### Claude Desktop setup

Use this JSON in Claude Desktop's MCP configuration.

#### Cursor setup

Add the same server object to Cursor's MCP settings.

#### VS Code setup

Add the server URL to the MCP server configuration used by your VS Code client.

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/website-email-extractor"
    }
  }
}
```

Example prompts:

- “Run Website Email Extractor on these five public domains and return email, websiteDomain, and sourceUrl.”
- “Extract displayed contact emails from this organization website with a maximum depth of one.”
- “Save the resulting website email records for spreadsheet export.”

### Proxy configuration

Direct HTTP requests are the default and normally the lowest-cost option. You may enable Apify Proxy through `proxyConfiguration` for targets that restrict direct cloud traffic.

There is no automatic residential-proxy fallback. The Actor uses only the proxy configuration you provide, so it does not silently switch to a higher-cost network route.

A proxy does not guarantee access to login-only pages, CAPTCHA-protected content, or websites that prohibit automated requests.

### Limits and failure behavior

- Only anonymously reachable public HTTP and HTTPS pages are supported.
- The Actor does not render client-side JavaScript.
- It follows high-signal contact links, not every internal link.
- PDF, image, document, and non-text email extraction is not supported.
- Login walls, CAPTCHAs, and some anti-bot systems can prevent extraction.
- Obfuscation outside the documented `[at]` and `[dot]` forms may not be recognized.
- A successful run may return no records when target pages display no email address.
- Individual page failures are logged and do not prevent other supplied websites from being processed.

Use smaller batches when diagnosing target-specific behavior. Inspect run logs for HTTP status or content-type warnings.

### Responsible use and legality

Use this Actor only for public information you are authorized to collect and process. Respect website terms, robots guidance where applicable, applicable privacy and data-protection law, and requests to remove or correct data.

A displayed email address is not automatic consent for marketing. Before outreach, determine a lawful basis, honor suppression lists and opt-outs, identify the sender, and follow rules applicable to your jurisdiction and recipients.

Do not use the Actor to access private systems, evade authentication, collect sensitive personal data, or facilitate spam, harassment, phishing, or discrimination.

### Troubleshooting

#### Why did a website return no emails?

Check whether the address is visible in the raw public HTML. If it appears only after browser interaction, inside an image or PDF, or behind a login, this Actor will not see it. Increase `maxPagesPerWebsite` or `maxDepth` only if a relevant contact page was not reached.

#### Why was a contact page skipped?

The link must be same-site and contain a recognized contact-related hint. Supply that public page directly in `startUrls` when its path or label is unusual.

#### Why do I see the same email more than once?

The same address on different source pages is retained as separate provenance. Deduplicate only by `email` downstream if page-level evidence is not important to your workflow.

#### Why did the run reject my URL?

Only public HTTP or HTTPS targets are accepted. Localhost and private-network addresses are blocked. Check for a missing or malformed domain.

#### Should I enable a proxy?

Use direct requests first. Enable a proxy only when you have evidence the public target blocks your current network route and automated access remains permitted.

### Related Automation Lab Actors

- [Bulk Company Domain Finder](https://apify.com/automation-lab/company-domain-finder) — find likely company domains before email extraction.
- [Google Maps Email Extractor](https://apify.com/automation-lab/google-maps-email-extractor) — discover local businesses and their public contact details from a Maps-driven workflow.
- [Website Contact Details Extractor](https://apify.com/automation-lab/website-contact-details-extractor) — use when you need website-level records containing phones, addresses, social profiles, and contact pages in addition to emails.

Choose Website Email Extractor when the desired output unit is each displayed email with exact page provenance.

### FAQ

#### Does the Actor verify whether an email mailbox exists?

No. It extracts and normalizes displayed addresses but does not perform MX, SMTP, deliverability, or identity verification.

#### Can it crawl multiple domains in one run?

Yes. Add multiple entries to `startUrls`; page limits apply independently to each supplied website.

#### Can it follow subdomains?

Yes, when `includeSubdomains` is enabled. Leave it disabled for tighter scope.

#### Does it generate guessed employee emails?

No. The Actor emits only addresses found in supported public page content.

#### Can I schedule it?

Yes. Save the input as an Apify Task and attach an Apify Schedule or webhook.

#### How do I stop after a fixed number of records?

Set `maxItems`. The Actor stops saving records after reaching the accepted-result limit.

# Actor input Schema

## `startUrls` (type: `array`):

Public website URLs or domains to crawl. The Actor checks each supplied page and high-signal same-site contact pages.

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

Stop after saving this many unique email-and-source-page records across all websites.

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

Maximum supplied and contact-related pages fetched for each website.

## `maxDepth` (type: `integer`):

Number of same-site contact-link levels to follow. Zero checks only each supplied URL.

## `includeSubdomains` (type: `boolean`):

Allow high-signal contact links on subdomains of the supplied website.

## `proxyConfiguration` (type: `object`):

Optional Apify Proxy settings. Direct requests are used by default; no paid residential fallback is automatic.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.w3.org/Consortium/contact"
    },
    {
      "url": "https://www.gnu.org/contact/"
    }
  ],
  "maxItems": 20,
  "maxPagesPerWebsite": 10,
  "maxDepth": 2,
  "includeSubdomains": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Open the default dataset overview containing extracted emails, domains, source URLs, discovery context, and timestamps.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.w3.org/Consortium/contact"
        },
        {
            "url": "https://www.gnu.org/contact/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/website-email-extractor").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 = { "startUrls": [
        { "url": "https://www.w3.org/Consortium/contact" },
        { "url": "https://www.gnu.org/contact/" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/website-email-extractor").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 '{
  "startUrls": [
    {
      "url": "https://www.w3.org/Consortium/contact"
    },
    {
      "url": "https://www.gnu.org/contact/"
    }
  ]
}' |
apify call automation-lab/website-email-extractor --silent --output-dataset

```

## MCP server setup

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

```

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/7hzDDeZAayqpt3TTD/builds/kQYmachLsJYmRQfLC/openapi.json
