# Website Email Address Scraper (`automation-lab/verified-website-contact-extractor`) Actor

Extract displayed website emails and same-page phones with source evidence, deduplication, role and suspicious flags, syntax checks, and mail-domain DNS verification.

- **URL**: https://apify.com/automation-lab/verified-website-contact-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

from $1.45 / 1,000 contact extracteds

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/actors/running/actors-in-store.md#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 Address Scraper

Extract each displayed **website email address** from supplied public company and organization websites, match phone numbers shown on the same pages, remove duplicates, classify shared inboxes, and verify email syntax and mail-domain DNS.

The Actor returns one source-attributed row per unique website and email pair. It is built for recurring lead-list cleanup where evidence and accuracy matter more than collecting unsupported guesses.

### What this website email finder does

For every supplied website, the Actor:

1. fetches the supplied page;
2. follows a bounded set of same-site contact, about, team, support, privacy, sales, and legal links;
3. extracts emails from visible text, `mailto:` links, and common `[at]` / `[dot]` obfuscation;
4. associates phones displayed on the same source pages;
5. deduplicates repeated occurrences of the same email on one website;
6. flags role-based and obviously suspicious inboxes;
7. checks normalized syntax and the email domain's MX or address-fallback DNS records; and
8. saves source URLs and a short discovery context with every result.

It does not invent emails from employee names or company-domain patterns.

### Who is it for?

- **Sales operations teams** cleaning company URL lists before CRM import.
- **Lead researchers** who need cited public evidence for every contact.
- **Recruiters and partnerships teams** finding displayed business inboxes and phones.
- **Data teams** normalizing contact data into JSON, CSV, Excel, or a warehouse.
- **Agencies** running the same bounded extraction process across client-provided websites.
- **Compliance-conscious teams** that need a distinction between domain verification and mailbox deliverability.

### Why use this website email extractor

A simple page regex often loses the source page, repeats footer addresses, accepts test inboxes, or calls an address “deliverable” without evidence.

This Actor adds a reusable cleanup layer:

- one normalized contact row per website/email pair;
- all observed source pages retained in `sourceUrls`;
- same-page phones instead of unrelated site-wide phone guesses;
- explicit `roleBased`, `role`, `suspicious`, and `suspiciousReasons` fields;
- MX and RFC-compatible address-fallback checks;
- no SMTP mailbox probing and no misleading mailbox-valid claim;
- bounded crawling and limits suitable for scheduled runs; and
- SSRF protection that rejects local and private-network targets.

### What data is extracted

| Field | Meaning |
| --- | --- |
| `websiteUrl` | Supplied public website URL |
| `websiteDomain` | Normalized supplied website domain |
| `email` | Normalized displayed email address |
| `emailDomain` | Domain part of the email |
| `sourceUrl` | First page where the email appeared |
| `sourceUrls` | Every crawled page where that email appeared |
| `phoneNumbers` | Phones shown on those same source pages |
| `context` | Short visible text or link context |
| `roleBased` | Whether the address is a common shared inbox |
| `role` | Recognized role such as `sales` or `support` |
| `suspicious` | Obvious fake, placeholder, test, or no-reply signal |
| `suspiciousReasons` | Reasons behind the suspicious classification |
| `syntaxValid` | Syntax validation result; emitted rows are valid |
| `domainStatus` | `mx`, `address-fallback`, `no-mail-records`, or `dns-error` |
| `domainAcceptsMail` | DNS evidence that the domain can accept mail |
| `mxRecords` | Ordered mail exchanger hostnames |
| `verificationLevel` | `syntax-and-domain` or `syntax-only` |
| `mailboxDeliverability` | Always `not-tested` |
| `scrapedAt` | ISO 8601 extraction timestamp |

### How to run the Actor

1. Open the Actor input page.
2. Add one or more real public company or organization URLs to **Company website URLs**.
3. Keep the default page and depth limits for a normal contact-page crawl.
4. Enable **Exclude role-based inboxes** when the downstream list requires person-like inboxes only.
5. Keep **Exclude suspicious inboxes** enabled for normal cleanup work.
6. Run the Actor.
7. Open the **Verified contacts** dataset view.
8. Export JSON, CSV, Excel, XML, or connect the dataset API to your pipeline.

The prefilled input uses real public organization contact pages and produces useful contact rows.

### Input parameters

#### `startUrls`

Required list of public HTTP or HTTPS pages. Strings and request-list objects are accepted. Duplicate URLs are removed. URLs with credentials, local hostnames, literal private IPs, or domains resolving to non-public addresses are rejected.

#### `maxItems`

Maximum deduplicated email rows across the run. Default: `100`. Range: `1` to `10,000`.

#### `maxPagesPerWebsite`

Maximum fetched pages for each supplied website. Default: `8`. Range: `1` to `50`.

#### `maxDepth`

Maximum relevant-link depth from the supplied page. Default: `2`. Use `0` to inspect only the supplied page.

#### `includeSubdomains`

Allows relevant links on subdomains of the supplied website. External domains are never crawled. Default: `false`.

#### `excludeRoleBased`

Skips recognized shared inboxes such as `info@`, `sales@`, `support@`, `billing@`, and `webmaster@`. Default: `false`, so these useful business contacts remain available with flags.

#### `excludeSuspicious`

Skips obvious fake, test, placeholder, no-reply, reserved-domain, and example inboxes. Default: `true`.

#### `proxyConfiguration`

Optional Apify Proxy settings. Direct HTTP is the default. The Actor does not silently turn on residential traffic or a browser fallback.

### Example input

```json
{
  "startUrls": [
    { "url": "https://www.apache.org/foundation/contact" },
    { "url": "https://www.gnu.org/contact/" }
  ],
  "maxItems": 50,
  "maxPagesPerWebsite": 5,
  "maxDepth": 1,
  "excludeRoleBased": false,
  "excludeSuspicious": true
}
```

For a strict CRM cleanup, set `excludeRoleBased` to `true`.

### Example output

This shortened record reflects the current output shape from a real public contact page:

```json
{
  "websiteUrl": "https://www.apache.org/foundation/contact",
  "websiteDomain": "apache.org",
  "email": "press@apache.org",
  "emailDomain": "apache.org",
  "sourceUrl": "https://www.apache.org/foundation/contact",
  "sourceUrls": ["https://www.apache.org/foundation/contact"],
  "phoneNumbers": [],
  "context": "press@apache.org",
  "roleBased": true,
  "role": "press",
  "suspicious": false,
  "suspiciousReasons": [],
  "syntaxValid": true,
  "domainStatus": "mx",
  "domainAcceptsMail": true,
  "verificationLevel": "syntax-and-domain",
  "mailboxDeliverability": "not-tested",
  "scrapedAt": "2026-08-30T06:41:11.690Z"
}
```

Download the default dataset as CSV when the final deliverable needs spreadsheet columns.

### How deduplication and matching work

The deduplication key is the normalized website plus normalized email address.

When the same email appears on a home page and contact page, the Actor emits one row, retains both pages in `sourceUrls`, and unions phones from pages where that email was actually displayed.

The same shared address can still appear once for each separately supplied website. This preserves company attribution instead of globally discarding a valid repeated contact.

`maxItems` applies after filtering and deduplication. Rejected, duplicate, suspicious-filtered, and role-filtered values are not charged as contact results.

### What email verification means

Verification has two bounded levels:

- **Syntax:** the normalized address has a valid-looking local part and multi-label domain.
- **Domain DNS:** the domain publishes MX records, or it has an RFC-compatible A/AAAA fallback when no MX record exists.

`domainStatus: "no-mail-records"` means DNS does not currently show a mail route. `dns-error` means the DNS check could not produce a definitive result.

The Actor does **not** send email, open SMTP conversations, verify a named mailbox, check catch-all policy, or promise delivery. `mailboxDeliverability` is therefore always `not-tested`.

### How much does it cost to extract verified website contacts?

Pay-per-event pricing consists of a **$0.005 start fee per run** plus one `contact` event for each deduplicated contact row saved.

The current BRONZE contact rate is **$0.002416 per contact**. Higher-volume tiers decrease from FREE through DIAMOND.

Approximate BRONZE examples before optional proxy infrastructure:

| Saved contacts | Start fee | Contact events | Total |
| ---: | ---: | ---: | ---: |
| 1 | $0.005 | $0.002416 | $0.007416 |
| 25 | $0.005 | $0.0604 | $0.0654 |
| 100 | $0.005 | $0.2416 | $0.2466 |
| 1,000 | $0.005 | $2.4160 | $2.4210 |

Actual billed event rates follow your Apify pricing tier. Optional proxy traffic and platform compute are separate infrastructure usage where applicable.

### Recurring lead-list cleanup workflow

A repeatable workflow can be built without adding unsupported monitoring claims:

1. keep the company website list in a dataset, spreadsheet, or key-value input;
2. schedule this Actor daily, weekly, or monthly;
3. export the current contact rows;
4. compare the new dataset to the previous run in your own pipeline;
5. route added, removed, or DNS-status-changed rows for review; and
6. write accepted contacts into the CRM.

The Actor produces timestamped snapshots. Apify schedules runs; change detection is performed by your downstream automation.

### API usage with cURL

Start a run and wait for dataset results:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~verified-website-contact-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{"url": "https://www.apache.org/foundation/contact"}],
    "maxItems": 10,
    "maxPagesPerWebsite": 2
  }'
```

Keep `$APIFY_TOKEN` in a secret or environment variable, never in source control.

### 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/verified-website-contact-extractor').call({
  startUrls: [{ url: 'https://www.apache.org/foundation/contact' }],
  maxItems: 10,
  maxPagesPerWebsite: 2,
});

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/verified-website-contact-extractor").call(
    run_input={
        "startUrls": [{"url": "https://www.apache.org/foundation/contact"}],
        "maxItems": 10,
        "maxPagesPerWebsite": 2,
    }
)

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use through Apify MCP

Add the Actor to Claude Code:

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

#### Claude Desktop

Add this remote MCP server in Claude Desktop's connector settings.

#### Cursor and VS Code

Cursor and VS Code MCP clients can use this JSON configuration:

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

Example prompts:

- “Extract displayed emails and same-page phones from these company websites and show the source URL.”
- “Run a strict cleanup that excludes role-based and suspicious inboxes.”
- “Return only contacts whose domain DNS indicates a mail route.”

### Integrations and exports

The default dataset works with:

- CSV and Excel exports for manual review;
- Google Sheets through an Apify integration or automation platform;
- webhooks triggered after successful runs;
- Make, Zapier, n8n, and custom ETL jobs;
- Apify API clients in JavaScript and Python; and
- dataset-to-database loading for CRM or warehouse enrichment.

Use `sourceUrl` as the evidence link and `email` plus `websiteDomain` as a stable downstream key.

### Limits and failure behavior

- Only anonymously reachable public HTML or plain-text pages are supported.
- JavaScript-only contact details may not be present in server-rendered HTML.
- CAPTCHA, authentication, consent walls, robots policy, or stable HTTP errors can prevent a page fetch.
- The crawler follows only relevant same-site links and stops at the configured page/depth limits.
- It does not crawl an entire domain, submit forms, log in, or solve challenges.
- One failed page is logged while other supplied websites continue.
- If no supplied website can be fetched, the run fails instead of reporting a misleading empty success.
- A successful run can legitimately contain zero rows when fetched pages display no valid email.
- DNS changes and transient resolver errors can change verification status between runs.

### Legality and responsible use

Extract only contacts intentionally displayed on public pages you are permitted to access.

You are responsible for complying with website terms, robots policies, privacy and data-protection law, direct-marketing rules, anti-spam law, and the lawful basis required for your intended use. A public email address is not automatic consent for unsolicited outreach.

Use suppression lists, honor opt-outs, minimize retention, protect exports, and avoid sensitive or personal targeting. Do not use the Actor to access private networks, authenticated data, or pages you do not have permission to process.

### Troubleshooting

#### Why did a website produce no contacts?

Confirm that its displayed email is present in server-rendered HTML, raise `maxPagesPerWebsite` if the contact page is deeper, and inspect the run log for HTTP or content-type errors. A visible browser page may still render its contact details only with JavaScript.

#### Why was `info@` returned?

Role inboxes are retained by default and labeled because many business workflows want them. Enable `excludeRoleBased` to skip recognized shared inboxes.

#### Why is a valid-looking address `syntax-only`?

Its domain had no usable mail route or the DNS lookup was inconclusive at run time. The field does not mean the mailbox was contacted.

#### Why are there no phone numbers for a row?

Phones are matched only when displayed on a source page where the email also appeared. The Actor does not attach an unrelated phone from another page.

#### Should I enable a proxy?

Use direct mode first. Enable an appropriate Apify Proxy configuration only for sites you are authorized to access that reject the direct route. There is no hidden automatic residential fallback.

### FAQ

#### Does it guess employee email addresses?

No. It exports only addresses displayed in fetched public page content or email links.

#### Does it verify mailbox deliverability?

No. It validates syntax and mail-domain DNS. `mailboxDeliverability` explicitly remains `not-tested`.

#### Are duplicate footer emails removed?

Yes. Repeated occurrences on one website become one row with merged source URLs and same-page phones.

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

Yes. Add multiple `startUrls` and set a suitable global `maxItems` limit.

#### Can I schedule it?

Yes. Use Apify schedules and compare timestamped output snapshots in your downstream workflow.

#### Does it charge for rejected duplicates?

No. The contact event is emitted only for a deduplicated row saved to the default dataset.

### Related automation-lab Actors

- [Website Email Extractor](https://apify.com/automation-lab/website-email-extractor) for a simpler displayed-email and source-context workflow.
- [Website Contact Details Extractor](https://apify.com/automation-lab/website-contact-details-extractor) for one broader website-level record containing contacts, addresses, and social profiles.
- [Bulk Email Address Verifier](https://apify.com/automation-lab/bulk-email-address-verifier) for syntax, MX, disposable-domain, and role checks on an email list you already have.

Choose this Actor when the input is a website list and the required output is one cleaned, source-attributed email row with same-page phone evidence.

# Actor input Schema

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

Public HTTP or HTTPS websites to crawl. Duplicate URLs are removed. Local, private-network, and credential-bearing URLs are rejected.

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

Maximum number of deduplicated email contact rows saved across all websites.

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

Maximum number of the home/start page and relevant contact, about, team, support, privacy, or legal pages fetched from each website.

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

Maximum relevant-link depth from each supplied start page. Use 0 for only the supplied page.

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

Allow relevant links on subdomains of the supplied website. External domains are never crawled.

## `excludeRoleBased` (type: `boolean`):

Skip common shared inboxes such as info@, sales@, support@, and billing@. Leave disabled to retain them with roleBased and role flags.

## `excludeSuspicious` (type: `boolean`):

Skip obvious test, fake, placeholder, no-reply, reserved-domain, and example inboxes. Enabled by default for clean lead lists.

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

Optional Apify Proxy settings. Direct requests are used by default; enable a proxy when supplied sites block datacenter traffic.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.apache.org/foundation/contact"
    },
    {
      "url": "https://www.gnu.org/contact/"
    }
  ],
  "maxItems": 20,
  "maxPagesPerWebsite": 8,
  "maxDepth": 2,
  "includeSubdomains": false,
  "excludeRoleBased": false,
  "excludeSuspicious": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Default dataset containing all emitted contact rows.

# 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.apache.org/foundation/contact"
        },
        {
            "url": "https://www.gnu.org/contact/"
        }
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/verified-website-contact-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.apache.org/foundation/contact" },
        { "url": "https://www.gnu.org/contact/" },
    ],
    "maxItems": 20,
}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/verified-website-contact-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/OhxGwECCIU0p8yyDU/builds/ZZAymnZwLlY1TWYCJ/openapi.json
