# Website Contact & Email Finder – MX-Verified Emails & Phones (`nourishing_courier/website-contact-email-finder`) Actor

Website contact & email finder: paste a list of domains and get emails (MX-verified, generic vs personal), phone numbers, LinkedIn, Facebook, Instagram, X, YouTube, TikTok, GitHub, address and company name in one row per site. No proxy, no API key. Pay only for sites where contacts were found.

- **URL**: https://apify.com/nourishing\_courier/website-contact-email-finder.md
- **Developed by:** [Ani Björkström](https://apify.com/nourishing_courier) (community)
- **Categories:** Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 website with contacts 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 Contact & Email Finder – MX-Verified Emails, Phones & Socials

**Website Contact & Email Finder** turns any list of websites or bare domains into a clean lead list: it crawls each site's homepage plus its contact, about, team, impressum and support pages and returns **emails, phone numbers, social media profiles, address and company name — one row per website**.

Three things set this email scraper apart from cheap "extract emails from websites" actors:

1. **MX-verified emails.** Every email domain gets a DNS MX lookup, so each address carries `mxValid: true / false / null`. Dead domains are flagged before they bounce in your outreach tool.
2. **Everything in one row.** Emails (labelled *generic* vs *personal*), phones (raw + E.164), LinkedIn, X/Twitter, Facebook, Instagram, YouTube, TikTok, GitHub, Threads and Pinterest, plus the best contact page, address and company name. No second enrichment step.
3. **You pay only for websites where contacts were found.** Unreachable sites, parked domains and pages without any contact data are delivered as explanatory rows — free.

No proxy, no API key, no login, no headless browser. Paste 5 domains or 50,000.

***

### What you get

One row per website. Arrays stay inside the row, so the dataset is flat, agent-friendly and MCP-ready.

| Field | Description |
|---|---|
| `url` | The website exactly as you entered it |
| `finalUrl` | Where the homepage actually resolved (www / https / redirects followed) |
| `status` | `ok` (contacts found, charged), `no_contacts_found` (free), `unreachable` (free) |
| `error` | Plain-English reason when the status is not `ok` |
| `companyName` | `og:site_name` → schema.org Organization name → page title |
| `primaryEmail` | The best address for outreach: on-site domain, MX-valid, `info@`/`contact@`/`hello@` first |
| `emails[]` | `{ email, type: "generic" \| "personal", mxValid, onSiteDomain, foundOn }` — sorted best-first |
| `emailCount` | Number of distinct emails after filtering |
| `primaryPhone` | First phone number in E.164 when it can be resolved, otherwise as written |
| `phones[]` | `{ raw, normalized, e164, foundOn }` |
| `phoneCount` | Number of distinct phone numbers |
| `linkedin`, `twitter`, `facebook`, `instagram`, `youtube`, `tiktok`, `github`, `threads`, `pinterest` | One canonical profile URL per network |
| `socials[]` | Every social profile URL found, canonicalised and de-duplicated |
| `contactPageUrl` | The contact / kontakt / impressum page that was found |
| `addressText` | Postal address from schema.org `PostalAddress` JSON-LD or an `<address>` block — `null` when the site does not state one, never guessed |
| `siteTitle`, `metaDescription`, `language` | Homepage title, meta description and `<html lang>` |
| `pagesCrawled` | How many pages were fetched for this site |
| `scrapedAt` | ISO 8601 UTC timestamp |

#### Example row (abridged)

```json
{
  "url": "https://www.netlight.com", "status": "ok", "companyName": "Netlight",
  "primaryEmail": "info@netlight.com", "emailCount": 18,
  "emails": [{ "email": "info@netlight.com", "type": "generic", "mxValid": true, "onSiteDomain": true, "foundOn": "https://www.netlight.com/contact" }],
  "primaryPhone": "+4686169940",
  "phones": [{ "raw": "+46 8 616 99 40", "normalized": "+4686169940", "e164": "+4686169940", "foundOn": "https://www.netlight.com/contact" }],
  "linkedin": "https://www.linkedin.com/company/netlight-consulting/",
  "instagram": "https://www.instagram.com/netlightconsulting/",
  "contactPageUrl": "https://www.netlight.com/contact",
  "addressText": "Herengracht 282, 1016 BX Amsterdam, Netherlands",
  "language": "en", "pagesCrawled": 4, "scrapedAt": "2026-09-22T18:41:07Z"
}
```

***

### How the website contact finder works

1. **Homepage first.** Bare domains get `https://`; `www`/non-`www` and `https`/`http` variants are tried, redirects followed, tracking parameters stripped.
2. **Smart page selection.** Same-domain links are scored by URL and anchor text — *contact, kontakt, about, team, impressum, imprint, support, help, press, careers, legal, privacy, om oss, über uns* — and the best `maxPagesPerSite − 1` pages are fetched; `/contact`, `/about`, `/kontakt`, `/impressum` are guessed as a fallback.
3. **Deep email extraction.** `mailto:` links, text regex, JSON-LD `Organization` data, obfuscations (`name [at] domain [dot] com`, `name (at) domain`, `name at domain dot com`, HTML entities) and Cloudflare `data-cfemail` protection are all decoded. Image filenames (`logo@2x.png`), `example.com`, Sentry/Wix/GoDaddy noise and `noreply@` are filtered out.
4. **Phone number scraper.** `tel:` links plus international patterns such as `+46 8 123 45 67`, `(415) 555-0132`, `0800 123 456` — returned as written, as digits, and as E.164 when the country can be inferred from a `+` prefix, the country TLD or the page language.
5. **Social media links extractor.** Profile links from headers, footers, social widgets and schema.org `sameAs`, canonicalised (`twitter.com/x` → `https://x.com/x`; share/intent/login links ignored); the profile matching the site's brand wins.
6. **MX verification.** Each unique email domain is resolved once (3 s timeout, cached across the run). No MX but a valid A record still counts as deliverable (RFC 5321); NXDOMAIN → `false`; timeout → `null`.
7. **Parked-domain protection.** Registrar parking pages and hosting placeholders ("website is currently not available", "domain for sale", nginx/Apache default pages) come back as `unreachable`, so you are never charged for a hosting provider's support address.

Pages are fetched with a desktop Chrome user agent, HTML only (bodies capped at 3 MB), one page at a time per site with a short pause. Websites run in parallel; one dead site never affects another.

***

### Input

```json
{
  "startUrls": [
    "https://www.netlight.com",
    "kvadrat.se",
    "www.zingermans.com",
    "https://russanddaughters.com/"
  ],
  "maxPagesPerSite": 8,
  "verifyEmailDomains": true,
  "includeGenericEmails": true,
  "concurrency": 10,
  "requestTimeoutSecs": 20
}
```

| Option | Default | What it does |
|---|---|---|
| `startUrls` | 3 demo sites | Websites to scan — full URLs or bare domains, one per line; `[{ "url": "..." }]` objects from other actors also work. |
| `maxPagesPerSite` | `8` | Homepage + up to this many contact-like pages (1–30). `2–3` is fast; `8` finds nearly everything on a business site. |
| `verifyEmailDomains` | `true` | DNS MX lookup per email domain → `mxValid`. |
| `includeGenericEmails` | `true` | Keep `info@`, `contact@`, `sales@`… Turn off for named / personal addresses only. |
| `concurrency` | `10` | Websites processed in parallel (1–50). |
| `requestTimeoutSecs` | `20` | Per-page timeout (5–120). |
| `proxyConfiguration` | off | Optional Apify Proxy — not needed for normal business websites. |

Running with no input scans the three demo websites, so you can see the output shape in one click.

***

### Use cases

- **Sales & SDR teams — bulk domain to email.** Export target-account domains from your CRM, run the finder, push `primaryEmail`, `primaryPhone` and `linkedin` back as enrichment. Only MX-valid addresses reach your sequences.
- **Local business prospecting for agencies.** Chain the email finder after the [Google Maps Lead Scraper](https://apify.com/nourishing_courier/google-maps-lead-scraper): Maps gives you the business website, this website contact scraper gives you the email, Instagram and Facebook page behind it.
- **Recruiters and staffing firms.** Find `careers@`, HR and hiring-manager addresses plus LinkedIn company pages for a list of employers.
- **PR, link-building and outreach.** Turn a list of blogs, publishers or partner sites into `press@` / editorial contacts with the correct social handles for a personalised pitch.
- **Market researchers.** Company name, language, address and social footprint for a whole industry list, exported to CSV or Excel.
- **AI agents and RAG pipelines.** Flat JSON rows with stable field names make this a drop-in contact-lookup tool for LLM agents via the Apify MCP server.

***

### Pricing

The website contact & email finder is pay-per-result: the `website` event is charged once per row with `status: "ok"` (at least one email, phone number or social profile delivered). Rows with `status: "no_contacts_found"` or `"unreachable"` (DNS failure, 403/5xx, parked domain, timeout) are free, so you still learn what happened to every input. No start fee, no charge per page crawled — raising `maxPagesPerSite` never costs more.

***

### Integrations

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("nourishing_courier/website-contact-email-finder").call(run_input={
    "startUrls": ["netlight.com", "kvadrat.se", "zingermans.com"],
    "maxPagesPerSite": 8,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["url"], row["primaryEmail"], row["primaryPhone"], row["linkedin"])
```

**JavaScript** (`npm i apify-client`):

```js
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: "YOUR_APIFY_TOKEN" });
const run = await client.actor("nourishing_courier/website-contact-email-finder").call({
  startUrls: ["netlight.com", "kvadrat.se", "zingermans.com"],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((r) => [r.url, r.primaryEmail, r.emails.length]));
```

**curl** (run synchronously and get the dataset back):

```bash
curl -X POST "https://api.apify.com/v2/acts/nourishing_courier~website-contact-email-finder/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls": ["netlight.com", "kvadrat.se"], "maxPagesPerSite": 5}'
```

**n8n, Make, Zapier.** Use the Apify node/module: trigger on a new row in Google Sheets, HubSpot or Airtable, run this actor with the domain, and write `primaryEmail`, `primaryPhone` and the social URLs back. Every input produces exactly one row, so lead-enrichment mapping is one-to-one.

**MCP / AI agents.** Add the actor to the Apify MCP server and your agent gets a `find website contacts` tool returning flat JSON it can reason over.

***

### Limits, legal & fair use

This actor only reads **publicly published business contact information** — the same addresses, phone numbers and social links a company puts on its own website for people to use. It sends plain HTTP requests at a gentle pace (pages within one site are fetched sequentially with a pause), does not log in anywhere, does not bypass paywalls or CAPTCHAs, and does not guess or generate email addresses.

Use the results responsibly. Under GDPR, contacting a business at a role or corporate address is generally covered by legitimate interest for B2B outreach, but you remain the data controller: honour opt-outs and unsubscribe requests, keep a lawful basis for personal (named) addresses, respect anti-spam rules such as CAN-SPAM and PECR, and never use this data for unsolicited consumer marketing or spam.

Technical limits worth knowing:

- **JavaScript-only sites.** Contact details rendered purely by client-side JavaScript (some React/Vue single-page apps) are invisible to a plain HTTP email scraper. WordPress, Wix, Squarespace, Shopify and Webflow sites expose contacts in the HTML and work fine.
- **Anti-bot walls.** A small share of sites behind aggressive Cloudflare or Akamai rules return 403 to datacenter traffic. Those come back as `unreachable` — free. Enabling the proxy option can help for such sites.
- **Caps.** 250 emails and 100 phones per site, 3 MB per page.
- Phone detection is pattern-based; on pages full of numeric codes a stray ID can occasionally slip through. `tel:` links and `+`-prefixed numbers are always reliable.

***

### FAQ

#### How do I extract emails from a list of websites in bulk?

Paste the domains into **Websites** (one per line — bare domains are fine), leave the defaults, and click Start. You get one row per domain with all emails found, MX-verified, plus phones and socials. Thousands of domains in one run are fine; raise **Parallel websites** to speed it up.

#### Does this email finder work without an API key or login?

Yes. It reads public web pages directly — no Hunter/Apollo/Clearbit key, no Google account, no proxy.

#### What does `mxValid` mean and how is it different from email verification services?

`mxValid` checks the DNS of the email's domain: `true` means the domain has mail servers (or an A record fallback) and can receive mail, `false` means it cannot, `null` means DNS timed out. It is not an SMTP mailbox check, so it cannot tell you whether `john@` specifically exists — but it removes every address on a dead or misspelled domain for free.

#### Can it tell generic emails from personal ones?

Yes. `info@`, `contact@`, `hello@`, `sales@`, `support@`, `press@`, `kontakt@` and about a hundred similar role addresses are labelled `generic`; named addresses like `anna.svensson@` are `personal`. Set **Include generic emails** to off to keep only personal ones.

#### Why is a website marked `unreachable`, and does the email finder charge for it?

The homepage could not be fetched as HTML — DNS failure, connection refused, HTTP 403/404/5xx, timeout, a parked domain or a hosting placeholder page. The `error` field says which. Unreachable and empty websites are never charged.

#### Does this website contact scraper work with n8n, Make or Zapier?

Yes. Use the Apify integration in any of them, pass the domain as `startUrls`, and map `primaryEmail`, `primaryPhone`, `linkedin`, `facebook`, `instagram` from the single output row back into your sheet or CRM.

#### Can I combine the email finder with Google Maps results?

Yes — run the [Google Maps Lead Scraper](https://apify.com/nourishing_courier/google-maps-lead-scraper) for "dentists in Austin" or "restauranger Stockholm", take the `website` column, and feed it here to get emails, Instagram and Facebook pages for every business.

# Actor input Schema

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

One website per line. Full URLs (https://acme.com) or bare domains (acme.com) both work; www and http/https are resolved automatically. The homepage plus the most likely contact pages of each site are scanned for emails, phone numbers and social profiles.

## `maxPagesPerSite` (type: `integer`):

Homepage plus up to this many contact-like pages (contact, about, team, impressum, kontakt, support, press...). 8 finds nearly everything on a normal business site; raise it for large corporate sites, lower it to 2-3 for speed.

## `verifyEmailDomains` (type: `boolean`):

Do a DNS MX lookup for every email domain so each email carries mxValid: true (domain accepts mail), false (domain cannot receive mail) or null (lookup timed out). Costs nothing extra and removes dead addresses before outreach.

## `includeGenericEmails` (type: `boolean`):

Keep role-based addresses such as info@, hello@, sales@, press@. Switch off to return only personal / named addresses. Every email is labelled generic or personal either way.

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

How many websites to process at the same time. Pages within one site are fetched one after another with a short pause, so each site only ever sees a gentle crawl.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one page before giving up on it. Slow sites get one retry.

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

Optional. Normal business websites do not need a proxy; turn one on only if you scan thousands of sites behind the same anti-bot vendor and start seeing 403s.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.eff.org",
    "https://creativecommons.org",
    "https://apify.com"
  ],
  "maxPagesPerSite": 8,
  "verifyEmailDomains": true,
  "includeGenericEmails": true,
  "concurrency": 10,
  "requestTimeoutSecs": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `contacts` (type: `string`):

One row per website with emails, phones, social profiles and metadata.

## `contactsCsv` (type: `string`):

The same rows as a spreadsheet-ready CSV file.

## `consoleView` (type: `string`):

Open the run's dataset in Apify Console.

# 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": [
        "https://www.eff.org",
        "https://creativecommons.org",
        "https://apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nourishing_courier/website-contact-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 = { "startUrls": [
        "https://www.eff.org",
        "https://creativecommons.org",
        "https://apify.com",
    ] }

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

```

## MCP server setup

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