# Smart Email Finder & Verifier — Website + DNS (`khadinakbar/smart-email-finder-verifier`) Actor

Find published company website emails from domains or CSV. Check syntax, DNS mail routing, mail provider, SPF, DMARC and selected DKIM records; preserve source pages and address flags. Also check an existing email list. Mailbox existence stays unknown.

- **URL**: https://apify.com/khadinakbar/smart-email-finder-verifier.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 published email found and checkeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

### Smart Email Finder & Verifier for company websites

Turn a list of company websites into published email addresses with source pages and syntax/DNS checks. The Actor follows relevant contact, help, team, about, and legal links, combines repeated appearances, and ranks the resulting addresses by evidence and contact relevance. It also checks an existing email list in `verify` mode. Import websites from a public CSV, identify the MX provider family, and inspect SPF, DMARC and selected DKIM DNS records alongside each eligible address.

Use it when your CRM, company research, or directory export already contains websites and you want to review contact information before enriching those records. Each result identifies the address, where it appeared, when it was checked, domain mail routing, and role/disposable/free-provider flags. Authentication checks add `mailProvider`, `spfRecords`, `dmarcRecords`, `dkimChecks`, and `authenticationStatus`. SPF/DMARC records describe domain configuration. A selected DKIM record is DNS evidence; these checks do not validate message signatures or mailbox acceptance. Disable `includeAuthenticationChecks` for the original lightweight syntax/DNS path, or supply up to 10 `dkimSelectors`.

**Verification means syntax and DNS mail routing.** `mail_server_found` means the domain has a mail route. `mailboxStatus` remains `unknown`: the Actor does not confirm an individual inbox, catch-all behavior, or deliverability. Ranking scores order published evidence; they are not probabilities. For mailbox-level decisions, continue with a verification service authorized for your workflow.

#### Quick start: find company website emails

```json
{
  "websites": ["https://www.python.org/about/help/"],
  "mode": "find",
  "maxWebsites": 1,
  "maxPagesPerWebsite": 3,
  "maxResults": 10
}
```

Domains such as `python.org` are accepted and use HTTPS. Supplying a known contact page gives the crawler a more focused starting point. Only linked pages on the same hostname, including its `www` form, are followed. URL query strings and fragments are removed. A domain is scanned once; the first supplied URL for that domain is retained.

#### Import a company website CSV

```json
{
  "csvUrl": "https://example.org/companies.csv",
  "csvColumn": "website",
  "maxWebsites": 10,
  "maxPagesPerWebsite": 5,
  "maxResults": 100
}
```

Use a public UTF-8 CSV with a header row. BOMs and quoted fields are supported. Files are bounded to 2 MB and 1,000 nonempty rows. Only values from `csvColumn` are retained. Direct `websites` and CSV websites are combined, with the first URL for each domain retained. Use ordinary public URLs; query strings are removed, and credentialed URLs and cross-host redirects are outside this import contract.

#### Check an existing email list

```json
{
  "mode": "verify",
  "emails": ["psf@python.org", "not-an-email", "sales@example.org"],
  "maxResults": 10,
  "maxConcurrency": 3
}
```

Every unique supplied address gets a check result, including malformed addresses. Domain case is normalized, while local-part case is preserved. The Actor identifies explicit MX records, domains that declare a null MX, and implicit routing through address records. Temporary DNS failures remain `unknown`. International and quoted local parts receive an unsupported-format explanation for review.

#### A practical company enrichment workflow

A research team starts with a spreadsheet of company websites. They paste those websites into `websites`, start discovery, and export `email`, `websiteDomain`, `sourceUrls`, `status`, and `reasonCodes`. Source pages allow the team to inspect why an address belongs in the company record. Role and disposable flags help route rows for review. A second `verify` call can check addresses already in the spreadsheet using the same output fields.

Start with [Google Maps Email Extractor](https://apify.com/khadinakbar/google-maps-email-extractor) when your starting point is a business search. Use [Bulk Website Contact Extractor](https://apify.com/khadinakbar/bulk-website-contact-extractor) when you also want phones and social links. This Actor focuses on email evidence and the combined discovery/check workflow.

### What one result contains

Find mode returns one unique **website/email pair**. An address published on two supplied websites can therefore appear twice with separate provenance. Verify mode returns one unique supplied address. A website with no usable email produces an uncharged `website_diagnostic` row with `email: null`.

| Field | How to use it |
| --- | --- |
| `recordType`, `recordId` | Distinguish email rows from coverage diagnostics and identify results stably. |
| `email`, `emailDomain`, `websiteDomain` | Join addresses back to company records. |
| `sourceUrls`, `extractionMethods` | Inspect the published evidence: mailto, visible text, bracket obfuscation, JSON-LD, or Cloudflare email encoding. |
| `status`, `verificationLevel`, `mailboxStatus` | Read the scope of verification before using a result. |
| `mailRoute`, `mxRecords` | Inspect explicit, implicit, null, missing, or unresolved domain routing. |
| `isRoleBased`, `isDisposable`, `isFreeProvider`, `isExternal` | Segment and review address characteristics; provider lists are incomplete. |
| `mailProvider`, `spfRecords`, `dmarcRecords`, `dkimChecks`, `authenticationStatus` | Review observed domain authentication records and selector coverage separately from mailbox existence. |
| `suggestedEmail`, `reasonCodes` | Review possible domain typos and the reasons behind each decision. Suggestions never replace the input. |
| `rankingScore`, `pagesScanned`, `checkedAt` | Order evidence, understand website coverage, and track freshness. |

The following is an illustrative complete email result. DNS records and timestamps reflect the time of each run.

```json
{
  "recordId": "0000000000000000000000000000000000000000000000000000000000000000",
  "recordType": "email",
  "website": "https://www.python.org/about/help/",
  "websiteDomain": "python.org",
  "email": "psf@python.org",
  "emailDomain": "python.org",
  "sourceUrls": ["https://www.python.org/about/help/"],
  "extractionMethods": ["mailto"],
  "isExternal": false,
  "mailProvider": "unknown",
  "spfRecords": [],
  "dmarcRecords": [],
  "dkimChecks": [],
  "authenticationStatus": "not_checked",
  "syntaxValid": true,
  "status": "mail_server_found",
  "verificationLevel": "syntax_dns",
  "mailboxStatus": "unknown",
  "mailRoute": "explicit_mx",
  "mxRecords": [{"exchange": "mail.python.org", "priority": 50}],
  "isDisposable": false,
  "isRoleBased": false,
  "isFreeProvider": false,
  "suggestedEmail": null,
  "reasonCodes": ["MX_PRESENT", "MAILBOX_NOT_CHECKED"],
  "rankingScore": 80,
  "pagesScanned": 1,
  "checkedAt": "2026-10-14T00:00:00.000Z"
}
```

### Inputs and work limits

| Input | Default and limits |
| --- | --- |
| `websites` | Public company domains or URLs; up to 100 supplied entries. Required for find mode. |
| `csvUrl`, `csvColumn` | Public UTF-8 website CSV, 2 MB / 1,000 rows; default column `website`. |
| `includeAuthenticationChecks` | Enabled; inspect SPF, DMARC and selected DKIM DNS records. |
| `dkimSelectors` | Up to 10 labels; defaults include `default`, `google`, `selector1`, `selector2`, `s1`, `s2`, `k1`. |
| `mode` | `find` by default; `verify` checks `emails`. |
| `emails` | Up to 5,000 supplied strings, processed within `maxResults`. Required for verify mode. |
| `maxWebsites` | 10 processed websites; range 1–100. |
| `maxPagesPerWebsite` | 5 attempted linked content pages; range 1–20. Robots requests are additional. |
| `maxResults` | 100 dataset rows; range 1–1,000, including uncharged website diagnostics. |
| `maxConcurrency` | 3 concurrent websites or address checks; range 1–10. Pages within a website are sequential. |
| `requestTimeoutSecs` | 15 seconds per content request including DNS and redirects; range 5–45. |
| `includeExternalEmails` | Enabled. Retains addresses on other domains, including a business's published free-provider address. |
| `includeRoleEmails` | Enabled. Retains department addresses such as sales and support. |

The crawler reads static HTML and follows relevant links. It respects robots.txt, uses a descriptive user agent, bounds redirects and response sizes, and spaces requests within a website. JavaScript-only content, logins, CAPTCHA-protected pages, unlinked pages, and offsite redirects need a different collection route. Missing contact data describes the pages actually read rather than the entire website.

### Pricing and cost control

The Actor uses **Pay per event** with **Apify platform usage** paid by the customer. Use the live [Pricing tab](https://apify.com/khadinakbar/smart-email-finder-verifier/pricing) when estimating the current event prices and platform rates.

| Event | Charge unit |
| --- | --- |
| Actor start | Platform startup event, scaled by allocated memory. |
| Published email found and checked | One persisted unique website/email pair; syntax/DNS/provider and enabled authentication checks are included. |
| Supplied email checked | One persisted unique supplied address in verify mode, including invalid-address results. |

Website diagnostics are uncharged. Repeated occurrences of one address on a website do not add email events. There is no external API key or third-party verification subscription in this implementation.

Estimate find-mode event cost as `startup + published website/email pairs × current email-found price`. Estimate verify-mode event cost as `startup + unique supplied addresses processed × current email-checked price`. Add platform compute, storage, and transfer usage separately. `maxResults` bounds rows, and Apify's run charge limit can stop processing earlier. The run status shows a ceiling before result work, and `RUN_SUMMARY` records accepted result-event counts; its event-cost figure excludes the startup event and platform usage.

### API, exports and agent use

Call asynchronously through the Apify API using an account token held in your environment:

```bash
curl --request POST \
  'https://api.apify.com/v2/acts/khadinakbar~smart-email-finder-verifier/runs' \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"websites":["https://www.python.org/about/help/"],"maxPagesPerWebsite":3,"maxResults":10}'
```

Read the returned run ID until it is terminal, then fetch the dataset identified by `defaultDatasetId`. Export as JSON, CSV or Excel in Console. Read `OUTPUT` and `RUN_SUMMARY` in the default key-value store for outcome, coverage, accepted result events, stop reason, and dataset links. Dataset retrieval requires the caller's authorized access.

For an AI agent, a useful prompt is:

> Find published contact emails for these company websites. Keep source URLs and role flags. Read the run summary, return the checked addresses, and describe mail-server evidence separately from mailbox existence.

Choose this Actor when the starting point is a website list or an email list and the required output is source-backed addresses with syntax/DNS evidence. Supply complete JSON input, wait for the run, and retrieve its dataset. It is a batch Actor with schema-defined inputs and outputs. Hosted MCP client execution and third-party destination delivery should be evaluated in the client you use.

### Best results and outcome recovery

`COMPLETE` means the requested checks finished with email results. `PARTIAL` means useful work exists but a request failed, robots restrictions reduced coverage, a limit stopped work, or execution was interrupted. `VALID_EMPTY` means the readable scoped pages had no eligible email, or robots restrictions prevented collection without a request failure. `INVALID_INPUT` explains what to correct. `UPSTREAM_FAILED` means content requests all failed without useful email results. `CONFIG_ERROR` means execution or storage needs attention.

Review source URLs and reason codes before retrying. Use a smaller batch or a known public contact URL for a focused crawl. The Actor preserves time for its terminal records and reports `runtime_deadline` when it stops before the platform timeout. A pending storage effect is preserved on interruption; the Actor stops an ambiguous replay instead of risking duplicate billing. Inspect the dataset and run charge ledger before deciding how to reconcile that run. Changed inputs or builds need a fresh run storage scope.

#### Builder's note

I found during fixture testing that adjacent menu labels can merge into an address-shaped string if a scraper flattens HTML carelessly. This implementation separates text nodes, extracts explicit mailto and structured evidence, and combines source pages. That makes the evidence behind a result easier to inspect than an address-only regex dump. I also reduced checkpoint writes: each new pending marker includes preceding acknowledged effects, and the finalizer flushes the last effect. An ambiguous effect still stops automatic replay.

#### Data use

Collect only information you are authorized to collect, respecting applicable laws, privacy rules, and each site's terms of service. Public visibility does not itself grant permission for outreach. The Actor sends no email, stores results in the caller's Apify storage, and uses no shared contact database. Configure your Apify retention and remove collected data when your purpose is complete.

# Changelog

This Actor's version history is a separate document: https://apify.com/khadinakbar/smart-email-finder-verifier/changelog.md

# Actor input Schema

## `websites` (type: `array`):

Use this to find published company contact emails. Enter domains or public HTTP/HTTPS URLs, such as python.org/about/help/. Supply up to 100 websites; the first URL for each domain is used. Contact links are prioritized and robots.txt is respected.

## `mode` (type: `string`):

Choose find for website discovery or verify for supplied addresses. For example, use verify with the emails list. The default is find. Verification checks syntax and DNS routing while mailbox existence remains unknown.

## `emails` (type: `array`):

Use this in verify mode for an existing address list. Enter strings such as sales@example.org. Up to 5,000 inputs are accepted and maxResults bounds processing. Invalid addresses receive a result with reason codes.

## `maxWebsites` (type: `integer`):

Limit company domains processed. For example, set maxWebsites to 10. The default is 10, with bounds 1–100. A global result or billing limit may stop work earlier.

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

Limit HTML pages attempted per website. For example, set maxPagesPerWebsite to 5. The default is 5, with bounds 1–20. A global result or billing limit may stop work earlier.

## `maxResults` (type: `integer`):

Limit total email and diagnostic rows. For example, set maxResults to 100. The default is 100, with bounds 1–1000. A global result or billing limit may stop work earlier.

## `maxConcurrency` (type: `integer`):

Limit simultaneous website crawls or address checks. For example, set maxConcurrency to 3. The default is 3, with bounds 1–10. A global result or billing limit may stop work earlier.

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

Limit seconds per website request including DNS and redirects. For example, set requestTimeoutSecs to 15. The default is 15, with bounds 5–45. A global result or billing limit may stop work earlier.

## `includeExternalEmails` (type: `boolean`):

Keep published addresses whose domain differs from the website. For example, a business may publish a Gmail address. Enabled by default and flagged with isExternal. Source pages remain attached for review.

## `includeRoleEmails` (type: `boolean`):

Keep role addresses such as info, sales and support. For example, sales@example.org is marked isRoleBased. Enabled by default. Disable to focus discovery on other published addresses.

## `csvUrl` (type: `string`):

Import company websites from a public UTF-8 CSV file. For example, use https://example.org/companies.csv with a website header. The file is bounded to 2 MB and 1,000 rows. Use ordinary public URLs; credentials, signed query strings and cross-host redirects are outside this input contract.

## `csvColumn` (type: `string`):

Select the CSV header containing websites. For example, use website or company_url. The default is website and column names are case-sensitive. Only website values are imported; other CSV columns are discarded.

## `includeAuthenticationChecks` (type: `boolean`):

Inspect SPF, DMARC and selected DKIM records through DNS. For example, enable this to review a company domain alongside its email contacts. Enabled by default with per-run domain caching. These are domain-authentication observations, not proof that a specific mailbox exists or that signatures will validate.

## `dkimSelectors` (type: `array`):

Inspect these DKIM selector labels for each email domain. For example, google queries google.\_domainkey.example.org. Supply up to 10 distinct DNS labels, or an empty list for SPF and DMARC only. A missing selector does not prove that the domain has no DKIM configuration.

## Actor input object example

```json
{
  "websites": [
    "https://www.python.org/about/help/"
  ],
  "mode": "find",
  "emails": [
    "sales@example.org"
  ],
  "maxWebsites": 10,
  "maxPagesPerWebsite": 5,
  "maxResults": 100,
  "maxConcurrency": 3,
  "requestTimeoutSecs": 15,
  "includeExternalEmails": true,
  "includeRoleEmails": true,
  "csvUrl": "",
  "csvColumn": "website",
  "includeAuthenticationChecks": true,
  "dkimSelectors": [
    "google",
    "selector1",
    "selector2"
  ]
}
```

# Actor output Schema

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

Dataset containing evidence-backed email results and coverage diagnostics.

## `output` (type: `string`):

Machine-readable completion or partial state.

## `runSummary` (type: `string`):

Counts, stop reasons and accepted event counts.

# 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 = {
    "websites": [
        "https://www.python.org/about/help/"
    ],
    "mode": "find",
    "emails": [
        "sales@example.org"
    ],
    "maxWebsites": 10,
    "maxPagesPerWebsite": 5,
    "maxResults": 100,
    "maxConcurrency": 3,
    "requestTimeoutSecs": 15,
    "includeExternalEmails": true,
    "includeRoleEmails": true,
    "csvColumn": "website",
    "includeAuthenticationChecks": true,
    "dkimSelectors": [
        "google",
        "selector1",
        "selector2"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/smart-email-finder-verifier").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 = {
    "websites": ["https://www.python.org/about/help/"],
    "mode": "find",
    "emails": ["sales@example.org"],
    "maxWebsites": 10,
    "maxPagesPerWebsite": 5,
    "maxResults": 100,
    "maxConcurrency": 3,
    "requestTimeoutSecs": 15,
    "includeExternalEmails": True,
    "includeRoleEmails": True,
    "csvColumn": "website",
    "includeAuthenticationChecks": True,
    "dkimSelectors": [
        "google",
        "selector1",
        "selector2",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/smart-email-finder-verifier").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 '{
  "websites": [
    "https://www.python.org/about/help/"
  ],
  "mode": "find",
  "emails": [
    "sales@example.org"
  ],
  "maxWebsites": 10,
  "maxPagesPerWebsite": 5,
  "maxResults": 100,
  "maxConcurrency": 3,
  "requestTimeoutSecs": 15,
  "includeExternalEmails": true,
  "includeRoleEmails": true,
  "csvColumn": "website",
  "includeAuthenticationChecks": true,
  "dkimSelectors": [
    "google",
    "selector1",
    "selector2"
  ]
}' |
apify call khadinakbar/smart-email-finder-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,khadinakbar/smart-email-finder-verifier"
        }
    }
}
```

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/augpXbZeG90CIdfah/builds/Ai88Gy8UPY8WA5UKN/openapi.json
