# Website Mail Extractor (`mikolabs/website-mail-extractor`) Actor

Website Email Scraper is a powerful, lightweight, and stealthy web crawler designed to find and extract public email addresses from any website. Simply provide a list of starting URLs, and the scraper will follow internal links, prioritize key pages, and return a clean list of deduplicated emails.

- **URL**: https://apify.com/mikolabs/website-mail-extractor.md
- **Developed by:** [Mikolabs](https://apify.com/mikolabs) (community)
- **Categories:** Automation, Lead generation, Developer tools
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 email enrichments

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 Scraper

Website Email Scraper is a powerful, lightweight, and stealthy web crawler designed to find and extract public email addresses from any website. Simply provide a list of starting URLs, and the scraper will follow internal links, prioritize key pages, and return a clean list of deduplicated emails.

### ✅ Key Features

- **SPA-Ready (React, Vue, Angular):** Scrapes modern Single Page Applications (SPAs) by scanning local JavaScript script assets for hidden contact emails.
- **Sitemap XML Support:** Automatically discovers and parses the website's `sitemap.xml` for lightning-fast page indexing.
- **Stealth Mode & Anti-Blocking:** Built-in concurrency control, randomized user-agents, and Apify Proxy support to keep your scraper undetected.
- **Smart Priority Routing:** Targets high-value contact pages (like `/contact`, `/about`, `/team`) first to save crawl time.
- **Blog & Media Filters:** Automatically skips irrelevant blog directories and large media assets to maximize efficiency and control costs.
- **Deduplication:** Automatically dedupes email addresses across the entire run to prevent duplicate charges and messy datasets.

### 🏆 Benefits

- **Effortless Lead Generation:** Turn a list of company websites into verified prospect contact details in seconds.
- **High Deliverability:** Every email includes the exact source URL where it was found, allowing easy QA and highly personalized outreach.
- **Unbeatable Cost Control:** Because it uses direct HTTP crawling instead of heavy browser instances, compute unit consumption is extremely minimal, costing under $0.05 per 1,000 crawled pages.

### 💳 Pricing

This Actor uses the **Pay-per-event** pricing model. You are billed only for successful results:

- **$3.00 USD per 1,000 unique emails** ($0.003 USD per email).
- Deduplication is run-wide; you are **never charged twice** for the same email address in a single run.
- If a run extracts 0 emails, you pay nothing.

### 🚀 Quick start

1. Go to the **Input** tab in the Apify Console.
2. Enter one or more website URLs in the **Seed URLs** field.
3. Configure the optional boundaries (like **Max emails to scrape** or **Max pages per seed**).
4. Select your **Proxy configuration** (Apify US/Residential proxy is recommended).
5. Click **Start** to run the scraper and download your data in JSON, CSV, Excel, or HTML format once the run completes.

### ⚙️ How it works

Under the hood, the scraper performs the following steps:

1. **Sitemap Discovery:** First, it attempts to fetch the website's XML sitemap to immediately identify all key pages.
2. **Page Crawling:** Starting from your seed URLs, it crawls internal pages up to your configured crawl depth.
3. **Keyword Prioritizing:** It scores page paths using contact-related keywords so that pages containing `/contact`, `/about`, or `/team` are visited first.
4. **Email Extraction:** It searches for plain-text email addresses, `mailto:` anchor tags, HTML-obfuscated entities, and scans local JS resource bundles to locate emails in dynamic components.
5. **Deduplication & Output:** Found emails are normalized, cleaned of CDNs/spam domains/fake placeholders, deduplicated, and appended to the dataset with their corresponding seed and discovery URLs.

### 📊 Output

All scraped emails are saved in a structured dataset, available for download in CSV, JSON, XML, or Excel.

#### Example JSON output item:

```json
{
    "email": "contact@example.com",
    "pageUrl": "https://example.com/about-us",
    "seedUrl": "https://example.com"
}
```

#### Output Fields:

| Field Name | Type | Description |
| :--- | :--- | :--- |
| `email` | String | The normalized and cleaned email address. |
| `pageUrl` | String | The exact webpage URL where this email address was discovered. |
| `seedUrl` | String | The initial seed URL entered in the input from which the crawl started. |

### 🛠️ Input Parameters

You can configure the scraper with the following fields:

| Field Name | Type | Description | Default / Empty Behavior |
| :--- | :--- | :--- | :--- |
| `urls` | Array of Objects | **Required.** List of website URLs to crawl (e.g. `[{"url": "https://example.com"}]`). | Must contain at least 1 URL. |
| `deepSearch` | Boolean | Optional. If enabled, crawls deep into the website structure. If disabled, only crawls the main page and high-priority pages (e.g. contact, about, team). | Default is `false` (highly cost-effective). |
| `maxEmailsToScrape` | Integer | Optional. Stop the run after finding this many unique emails across all seeds. | `0` or empty crawls until finished. |
| `maxCrawlDepth` | Integer | Optional. Link-hops from the seed URL. Depth 1 crawls only the seed page. | Default is `3`. Max is `6`. |
| `maxPagesPerSeed` | Integer | Optional. Maximum number of pages the scraper will visit per seed URL to control costs. | Default is `50`. Max is `1000`. |
| `proxyConfiguration` | Object | Optional. Select proxies to bypass blocking. US/Residential proxies are recommended. | Enabled by default. |

#### Example Input configuration:

```json
{
    "urls": [
        { "url": "https://example.com" }
    ],
    "maxEmailsToScrape": 20,
    "maxCrawlDepth": 2,
    "maxPagesPerSeed": 30,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
}
```

### 🔍 Error Handling & Ignored Resources

- **Skipping Bad URLs:** Malformed URL entries are automatically detected, logged as warnings, and skipped so they do not break the crawl.
- **Resource Filtering:** Non-text files (such as `.zip`, `.pdf`, `.mp4`, `.png`, `.jpg`, etc.) are automatically ignored to save bandwidth and compute units.
- **CDNs & Noise Filtering:** Extracted emails are checked against blacklists to remove fake/placeholder emails (like `you@example.com` or `info@example.com`) and tracking domains (like `sentry.io` or `google-analytics.com`).
- **Resilient Runs:** HTTP 403 or 404 pages are skipped gracefully without aborting the crawler.

### 📝 Release Notes

- **v1.0.0:** Initial release. Built-in support for XML sitemaps, SPA script scanning, crawler caps, and custom blacklists.

### 🆘 FAQ & Support

- **Is it legal to scrape email addresses?** Scraping public emails for contact directories and lead indexing is generally legal. However, always ensure you comply with regional regulations such as CAN-SPAM (US) or GDPR (EU) when conducting cold outreach.
- **Can it parse JS obfuscation?** Yes, the scraper automatically decodes standard obfuscation schemes (HTML entities, JS strings, and atob schemes) by scanning page scripts.
- **Support:** If you run into issues, have questions, or want to suggest new features, please file a ticket in the **Issues** tab.

# Actor input Schema

## `urls` (type: `array`):

One or more website URLs to start crawling from. The actor follows internal links to discover emails across the whole site.

## `deepSearch` (type: `boolean`):

If enabled, crawls internal pages deep into the website structure. If disabled, crawls only the main page and high-priority pages (e.g. contact, about, team).

## `maxEmailsToScrape` (type: `integer`):

Stop emitting results after this many unique emails are found across the whole run. Set to 0 for unlimited.

## `maxCrawlDepth` (type: `integer`):

How many link-hops from the seed URL to follow. Depth 1 = seed page only. Depth 2 = seed + all pages it links to. Priority pages (contact, about, blog, team…) are always followed at any depth.

## `maxPagesPerSeed` (type: `integer`):

Maximum number of pages the crawler will visit per seed URL. Keeps costs predictable on large sites.

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

Select proxies to be used. Residential proxies are highly recommended.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://www.w3.org/contact/"
    },
    {
      "url": "https://mikolabs.xyz"
    }
  ],
  "deepSearch": false,
  "maxEmailsToScrape": 10,
  "maxCrawlDepth": 3,
  "maxPagesPerSeed": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "urls": [
        {
            "url": "https://www.w3.org/contact/"
        },
        {
            "url": "https://mikolabs.xyz"
        }
    ],
    "maxEmailsToScrape": 10,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("mikolabs/website-mail-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 = {
    "urls": [
        { "url": "https://www.w3.org/contact/" },
        { "url": "https://mikolabs.xyz" },
    ],
    "maxEmailsToScrape": 10,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("mikolabs/website-mail-extractor").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 '{
  "urls": [
    {
      "url": "https://www.w3.org/contact/"
    },
    {
      "url": "https://mikolabs.xyz"
    }
  ],
  "maxEmailsToScrape": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call mikolabs/website-mail-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=mikolabs/website-mail-extractor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/s3EM48i5jYE91FKZa/builds/X4E0fGqiepK0i2ICv/openapi.json
