# Website Email Finder: Company Emails, Phones and Socials (`pohjastudio/website-email-finder`) Actor

Find business email addresses, phone numbers and social profiles from company websites. Junk filtered, role addresses labelled, MX checked. Pay only for domains where a contact was found.

- **URL**: https://apify.com/pohjastudio/website-email-finder.md
- **Developed by:** [Pohja Studio](https://apify.com/pohjastudio) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 3 total users, 2 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.

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 Finder

Give it a list of company websites. Get back the email addresses, phone numbers and social profiles published on them — with the junk already removed.

**You pay only for domains where a contact belonging to that company was found.** Dead ends, unreachable sites and pages that only carry somebody else's address are returned as rows so you can join them back onto your list, and they cost nothing.

### What it is for

Most lead lists arrive without email addresses. A Google Maps or directory scrape gives you a company name, a phone number and a website — and then stops. This Actor is the step after that one: it reads each website and pulls out the addresses that are actually on it.

Paste the dataset ID of your previous run into **Or: dataset ID from a previous run**, tell it which field holds the website, and it enriches the whole list without you exporting anything.

### Input

| Field | What it does |
|---|---|
| `domains` | Websites to read. `example.com`, `www.example.com` and `https://example.com/about` all work. |
| `sourceDatasetId` + `sourceDatasetField` | Read the websites out of another Actor's dataset instead of typing them. |
| `maxPagesPerSite` | Homepage first, then the contact / about / imprint pages it links to. Default 5. |
| `excludeRoleAddresses` | Keep only named people, drop `info@`, `sales@`, `support@`. |
| `excludeFreeProviders` | Drop gmail.com, hotmail.com and similar. |
| `requireMx` | Drop addresses whose domain cannot receive mail at all. On by default. |
| `onlySiteDomain` | Drop addresses that belong to a different company. |
| `maxEmailsPerSite` | Cap per site, best first. |
| `includeSitesWithoutContacts` | Keep a free row for every dead end. On by default. |

### Output

One row per domain:

```json
{
  "domain": "kesko.fi",
  "finalUrl": "https://www.kesko.fi/",
  "pagesCrawled": 4,
  "primaryEmail": "harri.utoslahti@kesko.fi",
  "primaryEmailIsCompanyDomain": true,
  "emails": ["harri.utoslahti@kesko.fi", "viestinta@kesko.fi", "ir@kesko.fi"],
  "emailDetails": [
    { "email": "harri.utoslahti@kesko.fi", "type": "personal", "isSameDomain": true, "isFreeProvider": false, "mxFound": true }
  ],
  "phones": ["+358105321000"],
  "socials": { "linkedin": "https://www.linkedin.com/company/kesko", "facebook": "https://www.facebook.com/kesko" },
  "emailCount": 3,
  "companyEmailCount": 3,
  "status": "contacts-found"
}
```

`status` is one of `contacts-found`, `only-third-party-contacts`, `no-contacts`, `unreachable`.

### What makes the list usable

Anything can run a regular expression over a page. The work is in what you throw away, because every false positive costs you a bounce, and enough bounces cost you your sending domain.

Removed, with the reason recorded:

- **Image and asset filenames.** `logo@2x.png` and `hero@3x.jpg` are the most common false positive in this category.
- **Ordinary words that look like obfuscation.** `www.wikidata.org` is not `www.wikid@a.org`; `static.hsappstatic.net` is not `static.hsappst@ic.net`. Both came out of a real run before the patterns were tightened.
- **Form examples**, including localised ones: `matti@esimerkki.fi`, `max@beispiel.de`, `hello@example.com`.
- **"Our addresses look like this" notices**: `etunimi.sukunimi@`, `firstname.lastname@`.
- **Mailboxes nobody reads**: `noreply@`, `donotreply@`, disposable domains.
- **CMS and tracking noise**: Wix, Squarespace and Sentry boilerplate, build hashes.
- **Other companies' addresses.** A privacy notice quotes the regulator; documentation uses a fictional customer. Those are ranked last and never billed — a real run had `john.wick@thecontinental.com` as the best email for `apify.com`.

Recovered rather than discarded:

- **Cloudflare-protected addresses** (`data-cfemail`), HTML entities (`&#64;`) and percent-encoding.
- **Written-out obfuscation**: `boris (at) example (dot) fi`.
- **Addresses glued to a query string**: `05%7c02%7charri.utoslahti@kesko.fi` becomes `harri.utoslahti@kesko.fi`.

Kept but labelled, so you decide:

- `type: "role"` vs `"personal"` — a desk or a named human.
- `isSameDomain` — does this address belong to the company whose site it was on.
- `isFreeProvider` — sole traders really do run on gmail.
- `mxFound` — whether the domain accepts mail at all.

### About verification

`requireMx` checks DNS: does this domain accept mail. That check never rejects a working mailbox, so it is safe to leave on.

Per-mailbox SMTP probing is deliberately **not** done. Gmail and Outlook stopped answering it truthfully, and a "verified" flag built on it is a guess dressed up as a fact. If you need mailbox-level verification, run a dedicated verifier over the output.

### Expected results

Roughly 60–70% of ordinary company websites publish at least one usable address. Sites that publish none are usually large companies routing everything through a web form, or single-page sites with a contact form and no address. Those come back as `no-contacts` and cost nothing.

### Notes

Only public pages are read, at a polite rate, with no login and no browser automation. Contact details of businesses are personal data in the EU: you are the controller of what you do with the output, and cold outreach still needs a lawful basis under GDPR and the ePrivacy rules of the country you are mailing into.

# Actor input Schema

## `domains` (type: `array`):

Websites to look for contacts on. Any form works: example.com, www.example.com, or a full URL like https://example.com/about. One per line.

## `sourceDatasetId` (type: `string`):

Enrich the output of another Actor instead of typing domains. Paste the dataset ID of a Google Maps, directory or company-list run and this Actor will read the website column from it.

## `sourceDatasetField` (type: `string`):

Which field of the source dataset holds the website. Google Maps results use 'website'; some directory Actors use 'url' or 'domain'.

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

How many pages to read per site. The homepage is always read first, then the contact, about and imprint pages it links to. Raising this finds a few more addresses on large sites and costs more time, never more money — you are billed per domain with contacts, not per page.

## `excludeRoleAddresses` (type: `boolean`):

Keep only addresses that look like a named person. Useful when you are prospecting decision makers. Leave off when you want any way into the company: many small businesses publish nothing but info@.

## `excludeFreeProviders` (type: `boolean`):

Drop addresses on free mail providers. Note that sole traders and small firms often use exactly these, so this can remove real leads.

## `requireMx` (type: `boolean`):

Checks the DNS MX records of each address domain and drops the ones that cannot accept mail at all. This is the honest half of verification: it never rejects a working mailbox. Per-mailbox SMTP probing is not done, because Gmail and Outlook no longer answer it truthfully.

## `onlySiteDomain` (type: `boolean`):

Drop addresses that belong to somebody else. Company websites quote regulators in privacy notices, use fictional customers in documentation and list their agency's address in the footer — those are not leads for this company. They are always ranked last and never billed; turn this on to remove them from the output entirely.

## `maxEmailsPerSite` (type: `integer`):

Caps how many addresses are returned for one site, best first: named people before role addresses.

## `includeSitesWithoutContacts` (type: `boolean`):

Keeps a row for every domain you passed in, including the dead ends, so you can join the results straight back onto your original list. These rows are free.

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

How many websites to read at the same time. Higher is faster; lower is gentler on small hosts.

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

How long to wait for one page before giving up on it and moving on.

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

Stop after this many domains. You are charged only for the domains where at least one contact was found.

## Actor input object example

```json
{
  "domains": [
    "example.com",
    "https://www.another-company.fi/"
  ],
  "sourceDatasetField": "website",
  "maxPagesPerSite": 5,
  "excludeRoleAddresses": false,
  "excludeFreeProviders": false,
  "requireMx": true,
  "onlySiteDomain": false,
  "maxEmailsPerSite": 10,
  "includeSitesWithoutContacts": true,
  "concurrency": 10,
  "requestTimeoutSecs": 15,
  "maxItems": 1000
}
```

# Actor output Schema

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

Emails with role/personal labels and MX status, phone numbers, social profiles and the crawl outcome for every domain you passed in.

## `contactsInConsole` (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 = {
    "domains": [
        "apify.com",
        "vercel.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pohjastudio/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 = { "domains": [
        "apify.com",
        "vercel.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("pohjastudio/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 '{
  "domains": [
    "apify.com",
    "vercel.com"
  ]
}' |
apify call pohjastudio/website-email-finder --silent --output-dataset

```

## MCP server setup

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