# Bulk Email Verifier & Validator — Automation-Ready Results (`emastra/bulk-email-verifier`) Actor

Verify email addresses in bulk with clear, automation-ready results. Get valid/invalid/inconclusive outcomes, proceed/hold/suppress recommendations, reason codes, risk flags, deduplication, and optional in-place enrichment for lead records and Apify Datasets.

- **URL**: https://apify.com/emastra/bulk-email-verifier.md
- **Developed by:** [Emiliano Mastragostino](https://apify.com/emastra) (community)
- **Categories:** Automation, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 71.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 email verification results

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/platform/actors/running/actors-in-store#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

**Verify email addresses in bulk and get clear, automation-ready results.**

Bulk Email Verifier & Validator checks email syntax, domain mail infrastructure, and mailbox-level signals to classify addresses before they enter outreach, a CRM, or another automation.

Provide **one email or a bulk list** and get structured results with a technical verdict, a `proceed` / `hold` / `suppress` recommendation, stable reason codes, retryability, and explicit risk flags.

Already have leads in Apify? You can also verify emails **directly inside lead records or an Apify Dataset**. The Actor preserves every original field and appends an `emailVerification` result — no extracting emails, correlating responses, or joining data back together.

### ✉️ What can Bulk Email Verifier & Validator do?

- **Verify one or thousands of email addresses** using the same simple `emails` input.
- **Check syntax and domain mail infrastructure** before mailbox verification.
- **Perform mailbox-level verification** when an address passes local checks.
- Return a clear technical result: `valid`, `invalid`, `inconclusive`, or `not_performed`.
- Return an automation-ready recommendation: `proceed`, `hold`, or `suppress`.
- Explain every result with a stable, machine-readable `primaryReason`.
- Flag **catch-all, disposable, role-based, and free-provider** addresses.
- Distinguish uncertainty from errors instead of forcing every address into valid/invalid.
- **Deduplicate emails within a run** so repeated addresses are verified and billed only once.
- Write results incrementally during bulk runs.
- Verify emails inside **inline lead records or Apify Datasets** while preserving the original row context.
- Use results through the Apify API, webhooks, schedules, Make, Zapier, n8n, Google Sheets, and other integrations.

### 🎯 Why use this bulk email verifier?

#### Automation-ready results

A simple `valid` or `invalid` flag is often not enough for automated workflows.

Every processed address gets both a technical result and a practical recommendation:

| Verification result | Recommendation       | Typical meaning                                       |
| ------------------- | -------------------- | ----------------------------------------------------- |
| `valid`             | `proceed`            | Evidence supports using the address                   |
| `invalid`           | `suppress`           | Do not use the address                                |
| `inconclusive`      | `hold`               | Available evidence cannot safely confirm or reject it |
| `not_performed`     | `hold` or `suppress` | Verification could not or should not be performed     |

Your workflow can branch directly on `recommendation`, while `primaryReason`, `retryable`, risk flags, and diagnostics remain available when you need more detail.

#### Conservative handling of uncertainty

The Actor does not turn ambiguous evidence into false certainty.

For example, a catch-all domain may accept mail for addresses that do not actually exist. In that case, the individual mailbox cannot be confirmed, so the result is reported as:

```json
{
    "verificationResult": "inconclusive",
    "recommendation": "hold",
    "primaryReason": "catch_all"
}
```

Similarly, provider failures are reported as errors rather than disguised as verification uncertainty.

#### Predictable bulk verification

Bulk runs are designed to behave safely in automation:

- the same normalized email is verified once per run;
- duplicate occurrences reuse that result;
- internal retries do not create additional verification charges;
- results are written continuously rather than only at the end;
- completed results survive interrupted or aborted runs;
- `RUN_SUMMARY` records what was processed, reused, billed, or left unfinished.

#### Built for Apify workflows

Running the verifier as an Apify Actor gives you scheduling, webhooks, API access, integrations, monitoring, and Dataset storage.

You can use it as a standalone bulk email checker or as a verification step inside a larger Apify workflow.

### 🚀 How to verify email addresses in bulk

The simplest way to use the Actor is the `emails` input.

#### 1. Add the email addresses

```json
{
    "emails": ["ada@acme.com", "grace@example.com", "invalid-email"]
}
```

One address is fine; large lists use the same input.

#### 2. Start the Actor

Click **Start**. Results appear in the **Output** dataset incrementally while the run is still processing.

#### 3. Use the result in your workflow

A result contains the original address plus its verification data:

```json
{
    "email": "ada@acme.com",
    "emailVerification": {
        "recommendation": "proceed",
        "retryable": false,
        "verificationResult": "valid",
        "primaryReason": "mailbox_verified",
        "risk": {
            "catchAll": false,
            "disposable": false,
            "roleBased": false,
            "freeProvider": false
        }
    }
}
```

For most automated workflows:

- use `recommendation` to decide what to do;
- use `verificationResult` for the technical classification;
- use `primaryReason` to understand or report why;
- use `retryable` to decide whether trying again later could help.

### 🔍 How email verification works

The Actor progressively verifies each address.

#### 1. Syntax check

Malformed addresses are rejected locally.

#### 2. Domain and mail infrastructure check

The Actor checks whether the domain exists and whether it has usable MX or other mail-routing records.

Addresses that can already be resolved at this stage do not require mailbox verification.

#### 3. Mailbox-level verification

Addresses that survive the local checks are sent to the current mailbox-verification backend.

The Actor maps backend responses into its own stable result contract rather than exposing provider-specific statuses.

#### 4. Automation decision

The available evidence becomes:

- a `verificationResult`;
- a `recommendation`;
- a `primaryReason`;
- a `retryable` flag;
- risk flags and diagnostics.

### 🧩 Verify emails inside lead records and Apify Datasets

If your leads are already structured records, you do not need to extract their email addresses first.

Pass the records directly:

```json
{
    "records": [
        {
            "firstName": "Ada",
            "company": "Acme",
            "contact": {
                "email": "ada@acme.com"
            }
        },
        {
            "firstName": "Grace",
            "company": "Hopper Inc",
            "contact": {
                "email": "grace@hopper.test"
            }
        }
    ],
    "emailField": "contact.email"
}
```

The Actor returns each original record with an `emailVerification` field appended:

```json
{
    "firstName": "Ada",
    "company": "Acme",
    "contact": {
        "email": "ada@acme.com"
    },
    "emailVerification": {
        "recommendation": "proceed",
        "retryable": false,
        "verificationResult": "valid",
        "primaryReason": "mailbox_verified"
    }
}
```

The same workflow works with an existing Apify Dataset by providing its `datasetId`.

This avoids the usual:

**extract emails → verify emails → correlate responses → join results back to leads**

workflow.

For the first release, record mode supports **one email address per record**.

### 📥 Input

At least one input source must be supplied.

| Field        | Type     | Description                                                                                                                                      |
| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `emails`     | string\[] | One or more email addresses to verify.                                                                                                           |
| `records`    | object\[] | Inline records whose email should be verified while preserving the original record.                                                              |
| `datasetId`  | string   | Apify Dataset containing records to verify. Read lazily, page by page.                                                                           |
| `emailField` | string   | Field containing the email in `records` or Dataset items. Dot paths and array indexes are supported, e.g. `contact.email` or `contacts.0.email`. |

You can combine multiple input sources in one run. They share the same deduplication scope.

#### Email field detection

When using `records` or `datasetId`, you can set `emailField` explicitly.

For example:

```json
{
    "datasetId": "YOUR_DATASET_ID",
    "emailField": "contact.email"
}
```

If `emailField` is omitted, the Actor tries:

1. `email`
2. `emailAddress`
3. `emails`

and uses the first value that is a plain string.

A field containing an array of emails is not guessed automatically. Record mode currently supports one address per record; split multi-email records upstream or use the `emails` input instead.

#### Invalid or missing record values

A missing, empty, or non-string email value does not cause the row to disappear.

It is emitted with an appropriate `input_invalid` result and is not charged.

If the supplied `datasetId` cannot be read, the run fails before verification or billing begins rather than silently processing an incomplete Dataset.

Very large inputs are also protected by a safety ceiling. If the limit is reached, ingestion stops explicitly and the condition is reported in the run log, run summary, and status message.

### 📤 Email verification output

For the `emails` input, each dataset item has this structure:

```json
{
    "email": "ada@acme.com",
    "emailVerification": {
        "schemaVersion": 1,
        "recommendation": "proceed",
        "retryable": false,
        "verificationResult": "valid",
        "primaryReason": "mailbox_verified",
        "processingState": "processed",
        "email": {
            "original": "ada@acme.com",
            "normalized": "ada@acme.com",
            "domain": "acme.com"
        },
        "risk": {
            "catchAll": false,
            "disposable": false,
            "roleBased": false,
            "freeProvider": false
        },
        "diagnostics": {
            "syntaxValid": true,
            "domainStatus": "mx_found",
            "mailboxSignal": "accepted",
            "mailProvider": "aspmx.l.google.com",
            "verificationTier": "mailbox"
        },
        "processing": {
            "verifiedAt": "2026-08-16T09:04:11.482Z",
            "resultReused": false,
            "verificationAttempts": 1,
            "error": null
        },
        "source": {
            "type": "emails",
            "index": 0,
            "occurrenceId": "emails:0"
        }
    }
}
```

For record and Dataset inputs, the same `emailVerification` object is appended to the original record.

Results can be downloaded from Apify in formats including JSON, CSV, Excel, and HTML.

### 🧠 Understanding the verification results

#### Core fields

| Field                                  | Meaning                                                                        |
| -------------------------------------- | ------------------------------------------------------------------------------ |
| `emailVerification.recommendation`     | `proceed`, `hold`, or `suppress` — default decision for downstream automation. |
| `emailVerification.verificationResult` | `valid`, `invalid`, `inconclusive`, or `not_performed`.                        |
| `emailVerification.primaryReason`      | Stable machine-readable reason explaining the result.                          |
| `emailVerification.retryable`          | Whether another attempt later could reasonably produce a better result.        |
| `emailVerification.processingState`    | `processed`, `input_invalid`, `unprocessed`, or `error`.                       |

#### Risk flags

| Field               | Meaning                                                                              |
| ------------------- | ------------------------------------------------------------------------------------ |
| `risk.catchAll`     | Domain accepts addresses broadly, preventing confirmation of the individual mailbox. |
| `risk.disposable`   | Disposable or temporary email address.                                               |
| `risk.roleBased`    | Role address such as `info@`, `sales@`, or `support@`.                               |
| `risk.freeProvider` | Consumer email provider.                                                             |

A `null` risk flag means **not evaluated**, not `false`.

For example, if an address fails syntax validation, mailbox-level risk checks are unnecessary and the corresponding flags remain `null`.

#### Diagnostics

| Field                          | Meaning                                                                                     |
| ------------------------------ | ------------------------------------------------------------------------------------------- |
| `diagnostics.syntaxValid`      | Local syntax validation result.                                                             |
| `diagnostics.domainStatus`     | Domain/mail-routing result.                                                                 |
| `diagnostics.mailboxSignal`    | Mailbox verification signal.                                                                |
| `diagnostics.mailProvider`     | Primary MX host derived from DNS.                                                           |
| `diagnostics.verificationTier` | Deepest verification tier that produced evidence: `none`, `syntax`, `domain`, or `mailbox`. |

Possible `domainStatus` values include:

- `mx_found`
- `implicit_mx`
- `null_mx`
- `no_mail_records`
- `domain_not_found`
- `temporary_failure`
- `not_checked`

Possible `mailboxSignal` values include:

- `accepted`
- `rejected`
- `ambiguous`
- `not_established`
- `not_checked`

### 🏷️ Stable email verification reason codes

`primaryReason` is intended to be safe for downstream automation.

The Actor currently emits these reason codes:

| Code                        | Meaning                                                     | Result          | Recommendation | Retryable   | Billed |
| --------------------------- | ----------------------------------------------------------- | --------------- | -------------- | ----------- | ------ |
| `mailbox_verified`          | Mailbox-level evidence supports validity.                   | `valid`         | `proceed`      | No          | Yes    |
| `disposable_address`        | Technically reachable, but disposable.                      | `valid`         | `suppress`     | No          | Yes    |
| `mailbox_rejected`          | Mail server permanently rejected the recipient.             | `invalid`       | `suppress`     | No          | Yes    |
| `domain_not_found`          | Domain does not resolve well enough to receive mail.        | `invalid`       | `suppress`     | No          | Yes    |
| `no_mail_infrastructure`    | Domain publishes no usable mail routing.                    | `invalid`       | `suppress`     | No          | Yes    |
| `invalid_syntax`            | Address has invalid email syntax.                           | `invalid`       | `suppress`     | No          | No     |
| `catch_all`                 | Domain accepts broadly, so the mailbox cannot be confirmed. | `inconclusive`  | `hold`         | No          | No     |
| `verification_inconclusive` | Evidence does not justify valid or invalid.                 | `inconclusive`  | `hold`         | No          | No     |
| `missing_email`             | No usable email was present.                                | `not_performed` | `suppress`     | No          | No     |
| `invalid_email_value`       | Email field contained a non-string value.                   | `not_performed` | `suppress`     | No          | No     |
| `provider_error`            | Verification backend failed before producing a result.      | `not_performed` | `hold`         | Usually yes | No     |
| `internal_error`            | Actor failed while processing this item.                    | `not_performed` | `hold`         | Yes         | No     |
| `trial_limit_reached`       | Free-plan mailbox-verification allowance was exhausted.     | `not_performed` | `hold`         | Yes         | No     |

Existing reason-code meanings will not change silently. New codes may be added in future schema versions.

#### What does `inconclusive` mean?

The current verification backend cannot reliably distinguish every case of greylisting, temporary SMTP behavior, or providers that deliberately prevent mailbox enumeration.

These situations are conservatively mapped to:

```json
{
    "verificationResult": "inconclusive",
    "primaryReason": "verification_inconclusive",
    "recommendation": "hold",
    "retryable": false
}
```

The Actor does not claim that re-running such an address is likely to improve the result.

### ♻️ Bulk deduplication

The same normalized email address is verified **once per run**.

If it occurs multiple times:

- every input occurrence still receives its own output;
- later occurrences reuse the original verification;
- `processing.resultReused` is `true`;
- the original `verifiedAt` timestamp is preserved;
- the verification is billed only once.

Deduplication is intentionally conservative.

Domains are normalized by lower-casing and punycoding, but local parts are not merged case-insensitively.

Therefore:

```text
John@example.com
john@example.com
```

are treated as different addresses.

Plus tags are also preserved:

```text
ada@acme.com
ada+newsletter@acme.com
```

are distinct verification identities.

Deduplication applies within one run, not across separate runs.

### 🔗 Correlating bulk results with input

**Output order is not guaranteed.**

Some addresses can be resolved locally in milliseconds while mailbox checks take longer, so results may arrive in a different order from the input.

For `emails`, records, and Dataset input, every occurrence receives a stable:

```text
emailVerification.source.occurrenceId
```

that is unique within the run.

For structured records, you can also correlate using your own preserved fields.

### 🧱 Lossless lead-record enrichment

When using record or Dataset mode, the Actor preserves the original record.

- Original top-level fields are copied without renaming, flattening, pruning, or type coercion.
- Nested objects and arrays remain unchanged.
- The original email field is not modified.
- The new verification result is added under `emailVerification`.

If your input already contains an `emailVerification` field, it is preserved by moving it to:

```text
emailVerification__source
```

then, if necessary:

```text
emailVerification__source_2
emailVerification__source_3
...
```

The collision is recorded in the generated verification metadata.

If an element in `records` is not an object, it is preserved under `sourceValue` alongside the corresponding explanation.

`undefined` values follow normal JSON serialization behavior and are dropped; `null` values remain `null`.

### 🛡️ Bulk run reliability

Results are written to the output Dataset as soon as they are available.

This means an interrupted, migrated, or aborted run retains work that has already completed.

The Actor does not need to wait until the entire batch finishes before producing usable output.

### 📊 Run summary

Every run continuously maintains a `RUN_SUMMARY` record in the run's key-value store.

You can find it under:

**Storage → Key-value store → `RUN_SUMMARY`**

It summarizes:

- source items discovered and reached;
- unique email addresses;
- duplicate results reused;
- verification results by category;
- recommendations;
- processing and provider errors;
- billable results;
- known unprocessed items;
- reasons work was left unfinished.

This is useful for monitoring large automated runs and deciding whether anything needs attention afterward.

### 💳 Bulk email verification pricing

The Actor uses Apify **pay-per-event** pricing:

- a small run-start event;
- one verification event for each **unique email address that produces a substantive definitive result**.

Current event prices are available in the Actor's **Pricing** tab.

#### You are not charged for

- duplicate occurrences of the same address within a run;
- internal verification retries;
- syntactically invalid email addresses;
- missing, empty, or non-string email values;
- catch-all results;
- other inconclusive results;
- provider errors;
- Actor processing errors;
- rows that were never reached;
- mailbox verification declined because the free trial allowance was exhausted.

#### You are charged for definitive verification results

Billable outcomes currently include:

- verified mailbox;
- permanently rejected mailbox;
- domain not found;
- domain with no usable mail infrastructure.

In other words, repeated input and failed processing do not multiply verification charges.

### 🎁 Free-plan mailbox verification trial

Mailbox-level verification has a real third-party cost.

For this reason, **non-paying Apify accounts receive a capped mailbox-verification trial** rather than unlimited developer-funded verification.

Local syntax and domain checks remain available regardless of the mailbox allowance.

When the allowance has been exhausted, mailbox verification is not partially performed for the remainder of the run. Affected items are returned as:

```json
{
    "processingState": "unprocessed",
    "primaryReason": "trial_limit_reached",
    "recommendation": "hold"
}
```

They are not charged.

Any information established by the local verification tiers is still returned.

Upgrade to a paid Apify plan to use mailbox verification at normal scale.

The trial counter stores only an Apify account identifier and aggregate counts; it does not store email addresses or domains.

### 💡 Tips for email verification workflows

- **Use `recommendation` for automation.** It is the simplest field for branching downstream workflows.
- **Use `primaryReason` for analytics and reporting.** It explains why the recommendation was made.
- **Do not automatically treat `inconclusive` as invalid.** Some real mailboxes cannot be safely verified remotely.
- **Verify records directly when you already have structured leads.** It avoids an unnecessary extraction and join step.
- **Run deduplication is automatic.** You do not need to remove repeated addresses beforehand just to avoid duplicate verification charges.
- **Check `RUN_SUMMARY` for large jobs.** In particular, inspect known unprocessed items after interrupted or bounded runs.
- **Remember that deduplication is per run.** Starting another run is a new verification request.

### 🔐 Third-party email processing and privacy

Addresses that pass local syntax and domain checks are currently sent to **ValidatedMails**, a third-party email-verification service, for mailbox-level verification.

ValidatedMails is the Actor's **current backend**, not part of the public result contract.

Provider-specific status names, reason strings, scores, and identifiers are not exposed in the output. They are mapped into the backend-independent `emailVerification` schema documented above.

This means the verification backend can be replaced or supplemented without requiring downstream workflows to depend on provider-specific semantics.

Addresses resolved locally are never sent to the mailbox-verification provider. This includes:

- invalid syntax;
- domains that do not exist;
- domains with no usable mail infrastructure.

Input and output data are stored in your own Apify storage under normal Actor operation.

The Actor's operational logs do not contain complete email addresses.

### ❓ FAQ

#### Does `valid` guarantee email delivery?

No.

`valid` means the available technical verification evidence supports treating the address as usable under this Actor's verification model.

It does **not** guarantee:

- future delivery;
- inbox placement;
- avoidance of a spam folder;
- sender reputation;
- ownership of the mailbox by a particular person.

#### Why can a real email be `inconclusive`?

Some email providers deliberately prevent external systems from reliably determining whether an individual mailbox exists.

This is a limitation of remote email verification itself.

The Actor represents that state as `inconclusive` rather than converting it to `valid`, `invalid`, or an internal error.

#### Can I verify an Apify Dataset directly?

Yes.

Provide its `datasetId` and, if necessary, the field containing the email.

The Actor reads the Dataset and returns every record with an `emailVerification` block appended.

### 🛠️ Support

Found a bug or a result that looks wrong? Please open an issue from the Actor's **Issues** tab.

You can also contact the developer directly at **<emiliano.mastra@gmail.com>**.

# Actor input Schema

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

One or more email addresses to verify. Duplicates are verified once and billed once, and every entry still gets its own result row.

## `records` (type: `array`):

Lead records to verify in place. Every original field is returned unchanged, with the verification result appended under `emailVerification`. The email address is read from the field named in Email field, or auto-detected from `email`, `emailAddress`, `emails` (in that order).

## `datasetId` (type: `string`):

The ID of an Apify Dataset with lead records to verify in place — for example the output of a scraper or a lead-generation Actor. Rows are read lazily and returned enriched. If the dataset cannot be read, the run fails before anything is verified or charged.

## `emailField` (type: `string`):

Which field of each lead record holds the email address. Supports dot paths, including array indexes: `contact.email`, `contacts.0.email`. Applies to Lead records and Input dataset only; leave empty to auto-detect `email`, `emailAddress` or `emails`.

## Actor input object example

```json
{
  "emails": [
    "hello@apify.com"
  ]
}
```

# Actor output Schema

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

One item per input occurrence: your original record, plus the verification result under `emailVerification`.

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

Counts for the whole run, including anything that was not processed.

# 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": [
        "hello@apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("emastra/bulk-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": ["hello@apify.com"] }

# Run the Actor and wait for it to finish
run = client.actor("emastra/bulk-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": [
    "hello@apify.com"
  ]
}' |
apify call emastra/bulk-email-verifier --silent --output-dataset

```

## MCP server setup

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