# SMTP Email Verifier (`maximedupre/smtp-email-verifier`) Actor

SMTP email verifier for bulk list cleaning. Check syntax, MX records, SMTP RCPT TO responses, catch-all domains, disposable and role flags, confidence, and recommended actions without sending email.

- **URL**: https://apify.com/maximedupre/smtp-email-verifier.md
- **Developed by:** [Maxime Dupré](https://apify.com/maximedupre) (community)
- **Categories:** Lead generation, Developer tools, Automation
- **Stats:** 79 total users, 57 monthly users, 57.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.01 / 1,000 checked emails

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

### 📬 SMTP email verification for bulk email lists

For email marketers, sales teams, and developers, this SMTP email verifier checks bulk email lists before outreach, CRM import, or enrichment. It returns syntax, MX, SMTP, risk, and action data for each address, so you can sort addresses into send, review, retry, or suppress groups without sending an email.

- Get mailbox-level evidence for an outreach list with **[SMTP email verification](https://apify.com/maximedupre/smtp-email-verifier/examples/smtp-email-verifier-smtp-email-verification)**.
- Clean a CRM export into useful action buckets with **[SMTP email verifier for CRM cleanup](https://apify.com/maximedupre/smtp-email-verifier/examples/smtp-email-verifier-crm-cleanup)**.
- Check a CSV, pasted, or direct list with **[Bulk email verifier](https://apify.com/maximedupre/smtp-email-verifier/examples/smtp-email-verifier-bulk-email-verifier)**.
- Sort addresses for sending, review, retry, or suppression with **[Email list cleaning](https://apify.com/maximedupre/smtp-email-verifier/examples/smtp-email-verifier-email-list-cleaning)**.
- Review cautious decisions for domains that accept random recipients with **[Catch-all email checker](https://apify.com/maximedupre/smtp-email-verifier/examples/smtp-email-verifier-catch-all-email-checker)**.

#### ✅ Email checks and risk signals

Each saved dataset row covers one normalized email address. It combines the verification decision with syntax, MX, SMTP method, optional domain-health, risk, and failure details.

| Field group | What you get |
| --- | --- |
| Email result | `email`, `domain`, `status`, `verdict`, `sendable`, `confidence`, `qualityScore`, and `riskLevel` |
| Action fields | `decision`, `recommendedDisposition`, and `recommendedAction` for send, review, retry, or suppress workflows |
| Syntax evidence | Validity, syntax errors, and typo suggestions when available |
| MX evidence | MX status, detected provider, and sorted MX records |
| SMTP evidence | Verification method and error text when available |
| Risk flags | Catch-all, disposable, role-based, and free-provider signals |
| Domain health | SPF, DMARC, and common DKIM selector checks when enabled |

#### ▶️ Check an email list

1. Add addresses in **Email addresses**, paste copied text in **Paste emails**, or upload a CSV/TXT file. You can combine these sources.
2. Every run checks syntax, MX, and SMTP signals and can include catch-all, disposable, role, free-provider, and typo signals. Turn on **Domain health check** for SPF, DMARC, and common DKIM selector signals.
3. Set **Run limit** or **Per-domain limit** when you want to check only part of a list. Leave either limit empty to return all available results until the source is exhausted.
4. Run the Actor, then open the dataset to review status, confidence, risk flags, and recommended action for each email.

#### ⚙️ Input

You can provide emails in three ways and combine them in one run. The domain health check is optional and off by default.

**Input fields**

| Field | Type | What it does |
| --- | --- | --- |
| `emails` | array of strings | Add email addresses directly. Use one address per list entry. |
| `emailsText` | string | Paste raw CSV rows, copied CRM text, comma-separated values, or one email per line. |
| `csvFile` | string | Upload a CSV or TXT file from a spreadsheet or text export. |
| `checkDomainHealth` | boolean | Check SPF, DMARC, and common DKIM selectors for each email domain. |
| `maxEmails` | optional integer | Limit the normalized addresses checked in this run. Leave it empty to return all available results until the source is exhausted. |
| `maxEmailsPerDomain` | optional integer | Limit addresses checked for each email domain. Leave it empty to return all available results for each domain until the source is exhausted. |

The Actor normalizes submitted addresses and removes duplicate addresses before checking them. Both limits accept positive integers when set.

**Input example**

This complete example is the public input from a successful current-beta default-input run.

```json
{
  "emails": [
    "postmaster@gmail.com",
    "abuse@yahoo.com",
    "test@mailinator.com",
    "contact@cloudflare.com",
    "admin@apify.com"
  ],
  "emailsText": "",
  "csvFile": "",
  "checkDomainHealth": false
}
```

#### 🧾 Output

The default dataset contains one row for each checked email address. Status values include `valid`, `invalid`, `invalid_syntax`, `no_mx`, `catch_all`, `temporary_failure`, `blocked`, `timeout`, and `unknown`.

**Email verification row**

| Field | Type | What it does |
| --- | --- | --- |
| `email` | string | Normalized email address that was checked. |
| `domain` | string or null | Domain used for DNS and SMTP checks, or null when no usable domain is available. |
| `status` | string | Machine-readable verification outcome. |
| `verdict` | string | Human-readable verification result for filtering exports. |
| `sendable` | boolean | Whether the result can be used for sending according to the verification decision. |
| `confidence` | number from 0 to 1 | Verification confidence. |
| `qualityScore` | integer from 0 to 100 | List-quality score for sorting and suppression workflows. |
| `riskLevel` | string | Overall deliverability and list-quality risk. |
| `decision` | string | Compact action category for filtering and automation. |
| `recommendedDisposition` | string | Recommended list-management bucket for the email. |
| `recommendedAction` | string | Plain-English next step for this email. |
| `riskFlags` | array of strings | Specific risk signals found for this email. |
| `flags` | object | Boolean verification and list-quality signals. |
| `flags.isDisposable` | boolean | Whether the domain is a known temporary email provider. |
| `flags.isRoleBased` | boolean | Whether the local part is a shared role address. |
| `flags.isFreeProvider` | boolean | Whether the domain belongs to a common free mailbox provider. |
| `flags.isCatchAll` | boolean | Whether the domain appears to accept arbitrary recipients. |
| `syntax` | object | Email syntax validation details. |
| `syntax.isValid` | boolean | Whether the email passed syntax validation. |
| `syntax.errors` | array of strings | Syntax issues found in the email. |
| `syntax.suggestion` | string or null | Suggested correction for a common domain typo, or null when none is available. |
| `mx` | object | DNS mail-exchange evidence for the email domain. |
| `mx.hasRecords` | boolean or null | Whether the domain has usable MX records, or null when no domain was available. |
| `mx.provider` | string or null | Detected mail provider when recognized. |
| `mx.records` | array of objects | MX records sorted by priority. |
| `mx.records[].priority` | integer | MX record priority. |
| `mx.records[].exchange` | string | MX record host. |
| `smtp` | object | Mailbox-level SMTP recipient-check evidence. |
| `smtp.method` | string | Deepest verification method used for the row. |
| `smtp.error` | string or null | Connection or verification error when the mailbox check is inconclusive. |
| `domainHealth` | object or null | Email-domain authentication signals, or null when the check is not available. |
| `domainHealth.spf` | boolean | Whether the domain publishes an SPF record. |
| `domainHealth.dmarc` | boolean | Whether the domain publishes a DMARC record. |
| `domainHealth.dkim` | boolean | Whether a common DKIM selector was found. |
| `domainHealth.dkimSelectorsChecked` | array of strings | Common DKIM selectors checked for the domain. |
| `failureAnalysis` | object | Reasoning for suppress, retry, review, and cautious-send decisions. |
| `failureAnalysis.primaryCause` | string or null | Main reason for the decision, or null for clean deliverable emails. |
| `failureAnalysis.category` | string | High-level decision category. |
| `failureAnalysis.severity` | string | Impact level for the cause. |
| `failureAnalysis.recoverable` | boolean | Whether retrying, correcting, or replacing the email may resolve the issue. |
| `failureAnalysis.explanation` | string | Operator-readable explanation of the decision. |

**Output example**

This complete row is from a successful current-beta mixed-input run with domain health enabled.

```json
{
  "email": "abuse@yahoo.com",
  "domain": "yahoo.com",
  "status": "catch_all",
  "verdict": "Valid - Catch-all domain",
  "sendable": true,
  "confidence": 0.84,
  "qualityScore": 84,
  "riskLevel": "medium",
  "decision": "send_monitor",
  "recommendedDisposition": "send_with_caution",
  "recommendedAction": "Review according to source signals: free_provider, role_address, mailbox_accepts_mail, catch_all_domain.",
  "riskFlags": [
    "free_provider",
    "role_address",
    "mailbox_accepts_mail",
    "catch_all_domain"
  ],
  "flags": {
    "isDisposable": false,
    "isRoleBased": true,
    "isFreeProvider": true,
    "isCatchAll": true
  },
  "syntax": {
    "isValid": true,
    "errors": [],
    "suggestion": null
  },
  "mx": {
    "hasRecords": true,
    "provider": "Yahoo Mail",
    "records": [
      {
        "priority": 1,
        "exchange": "mta5.am0.yahoodns.net"
      },
      {
        "priority": 1,
        "exchange": "mta6.am0.yahoodns.net"
      },
      {
        "priority": 1,
        "exchange": "mta7.am0.yahoodns.net"
      }
    ]
  },
  "smtp": {
    "method": "smtp",
    "error": null
  },
  "domainHealth": {
    "spf": true,
    "dmarc": true,
    "dkim": true,
    "dkimSelectorsChecked": [
      "default",
      "selector1",
      "selector2",
      "google",
      "k1",
      "mail"
    ]
  },
  "failureAnalysis": {
    "primaryCause": "free_provider",
    "category": "deliverability_risk",
    "severity": "medium",
    "recoverable": true,
    "explanation": "Source reported a catch-all domain, so the specific mailbox needs cautious review."
  }
}
```

#### 💳 Pricing

This Actor charges one `email-verified` event for each checked email row that is saved to the dataset.

| Unit | Price |
| --- | ---: |
| 1 checked email | $0.00001 |
| 1,000 checked emails | $0.01 |

There is no actor-start charge. If no email row is saved, no checked-email event is charged.

#### 🔌 Integrations

https://www.youtube.com/watch?v=bNACk1_S_6w\&list=PLObrtcm1Kw6MUrlLNDbK9QRg8VDJg0gOW\&index=4

- Export checked emails to JSON, CSV, Excel, XML, RSS, or HTML from the Apify dataset.
- Use the Apify API to run SMTP email verification from your app or enrichment pipeline.
- Schedule repeat list checks and send finished datasets to webhooks or downstream Apify integrations.
- Combine results with website email discovery, CRM cleanup, cold outreach QA, or signup review workflows.

#### ❓ FAQ

##### Does this do a real SMTP check?

Yes. After syntax and MX checks, the Actor connects to the target mail server and performs an SMTP recipient probe with `RCPT TO`. It then closes the connection without sending a message.

##### Why does it check MX records first?

MX records tell the Actor which mail servers receive email for a domain. If a domain has no usable MX records, the Actor can return `no_mx` without wasting SMTP attempts.

##### Why are some results marked `unknown`, `blocked`, or `catch_all`?

Some mail servers hide mailbox validity, block probing, rate-limit connections, or accept all recipients. The Actor keeps those outcomes separate and gives a recommended action instead of pretending they are confirmed valid mailboxes.

##### Do I need Gmail, Outlook, SMTP, or verifier API credentials?

No. You only provide the email addresses you want to check. The Actor does not need inbox credentials, an email provider login, or a third-party verifier API key.

##### Can I use this for email list cleaning?

Yes. The output includes suppress, retry, review, send, and send-with-caution decisions so you can clean a list before importing it into a CRM, outreach tool, newsletter tool, or data pipeline.

##### Why not use an SMTP API?

SMTP is a protocol, not a single public API for checking every mailbox. This Actor wraps syntax, DNS, SMTP, catch-all, and list-quality checks into one Apify run so you can export the results or call them through the Apify API.

##### What are SMTP email verifier alternatives?

Alternatives include MX-only email checkers, paid email verification APIs, CRM validation tools, and lead-enrichment platforms. Use this Actor when you already have the email addresses and want credential-free SMTP evidence plus action-ready output.

### 📝 Changelog

**v1.0** (13-09-2026)

- Check email lists from direct entries, pasted text, or CSV/TXT files with run and per-domain limits.
- Get structured verification results with action recommendations and optional domain health checks.
- See honest running progress for open-ended lists instead of treating a run cap as the total.

**v0.1**

- Initial release.

### 🆘 Support

For issues, questions, or feature requests, [file a ticket](https://console.apify.com/actors/maximedupre~smtp-email-verifier/issues) and I'll fix or implement it in less than 24h 🫡

### 🔗 Related Actors

- [Website Email Scraper](https://apify.com/maximedupre/website-emails-scraper) - Find public contact emails from submitted websites before checking their mailboxes.
- [Email MX Verifier](https://apify.com/maximedupre/email-mx-verifier) - Pre-screen syntax, MX, disposable-domain, and role-address signals before deeper SMTP checks.
- [Bulk SMTP Email Verifier — Mailbox & Catch-All Checker](https://apify.com/automation-lab/smtp-email-verifier) - Compare another mailbox and catch-all checking workflow for bulk lists.
- [Real-Time SMTP Email Verifier \[Live Handshake\]](https://apify.com/thecfmarketing/bulk-email-verifier) - Review another SMTP verification option for email-list checks.
- [BounceVerify - Email Verifier & Deliverability Tester](https://apify.com/wyle/bounceverify) - Check syntax, DNS MX records, SMTP mailbox status, catch-all domains, and disposable emails in another workflow.

**Made with ❤️ by Maxime Dupré**

# Actor input Schema

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

Enter addresses directly. Use one row per address for the cleanest first run.

## `emailsText` (type: `string`):

Paste raw CSV rows, copied CRM text, comma-separated values, or one email per line.

## `csvFile` (type: `string`):

Upload a file when your emails already live in a spreadsheet export or text file.

## `checkDomainHealth` (type: `boolean`):

Check SPF, DMARC, and common DKIM selectors for each email domain.

## `maxEmails` (type: `integer`):

Maximum normalized email addresses to check in this run. Leave it empty to return all available results until the source is exhausted.

## `maxEmailsPerDomain` (type: `integer`):

Maximum addresses to check for one email domain in this run. Leave it empty to return all available results for each domain until the source is exhausted.

## Actor input object example

```json
{
  "emails": [
    "postmaster@gmail.com",
    "abuse@yahoo.com",
    "test@mailinator.com",
    "contact@cloudflare.com",
    "admin@apify.com"
  ],
  "emailsText": "",
  "csvFile": "",
  "checkDomainHealth": false
}
```

# Actor output Schema

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

Open the checked email results in the default dataset.

# 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 = {
    "emails": [
        "postmaster@gmail.com",
        "abuse@yahoo.com",
        "test@mailinator.com",
        "contact@cloudflare.com",
        "admin@apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maximedupre/smtp-email-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 = { "emails": [
        "postmaster@gmail.com",
        "abuse@yahoo.com",
        "test@mailinator.com",
        "contact@cloudflare.com",
        "admin@apify.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("maximedupre/smtp-email-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 '{
  "emails": [
    "postmaster@gmail.com",
    "abuse@yahoo.com",
    "test@mailinator.com",
    "contact@cloudflare.com",
    "admin@apify.com"
  ]
}' |
apify call maximedupre/smtp-email-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maximedupre/smtp-email-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/Gr2MhVwGbJKZFO7vC/builds/6pbbsqtFrbw3lVqSD/openapi.json
