The Mine Works
Bulk Email Verification in Python: MX, SMTP and Catch-All Detection Without an API
← All posts
tutorial June 30, 2026 · 4 min read

Bulk Email Verification in Python: MX, SMTP and Catch-All Detection Without an API

How to verify thousands of email addresses for free using DNS MX lookups and optional SMTP handshakes. No API key, no monthly subscription.

Try the scraper

The actor referenced in this article. Pay only for results delivered.

View the scraper →

A cold outreach list that is 30% invalid addresses is not a list problem — it is a delivery problem. Every hard bounce hurts your sending domain reputation. Email service providers track bounce rates and throttle or blacklist senders who stay above 2–3%. The fix is verifying addresses before they ever hit an outbox.

Most email verification tools charge a monthly subscription for something that is fundamentally a DNS lookup. This guide shows how to verify in bulk using the Email Verifier & Validator actor — no API key, $0.002 per address, nothing charged on failure — and how to wire it into a Claude agent that flags risky leads automatically.

What the Verification Checks

For each address the actor runs four checks in sequence:

  1. Syntax — RFC-style local@domain validation. Catches typos like user@@domain.com or missing TLDs.
  2. MX lookup — DNS query on the domain. No MX records means mail cannot be delivered regardless of what SMTP says.
  3. SMTP handshake — Connects to the domain’s best-priority MX server on port 25, sends HELO / MAIL FROM / RCPT TO, reads the response code, then quits without sending data. A 550 reply means the mailbox definitively does not exist.
  4. Catch-all probe — Sends a second RCPT for a randomly-generated address at the same domain. If the server accepts it, the domain accepts everything, which means a positive SMTP response does not prove your real address is deliverable.

The verdict is one of four values:

  • valid — syntax ok, MX found, SMTP accepted (and server is not catch-all)
  • invalid — bad syntax, no MX, or SMTP hard-rejected the mailbox
  • risky — disposable domain, role-based address (info@, sales@), or catch-all server
  • unknown — MX exists but SMTP was blocked/timed out (firewalled on port 25)

Verify a List in Python

import os
from apify_client import ApifyClient

apify = ApifyClient(os.environ["APIFY_TOKEN"])

emails = [
    "alice@stripe.com",
    "throwaway@mailinator.com",
    "no-reply@example.com",
    "ceo@notion.so",
]

run = apify.actor("themineworks/email-verifier-validator").call(run_input={
    "emails": emails,
    "checkMx": True,
    "checkSmtp": True,
    "detectDisposable": True,
    "detectRoleBased": True,
})

results = list(apify.dataset(run["defaultDatasetId"]).iterate_items())

for r in results:
    if r.get("_type") == "summary":
        continue
    print(f"{r['email']:40s}  {r['status']:10s}  {r['reason']}")

Output:

alice@stripe.com                          valid       Mailbox accepted by mail server
throwaway@mailinator.com                  risky       Disposable / throwaway domain
no-reply@example.com                      risky       Role-based address (not a person)
ceo@notion.so                             unknown     MX found; mailbox not confirmed (SMTP inconclusive)

Use It as an MCP Tool in Claude

The actor ships as an MCP endpoint. Add it to Claude Desktop’s config:

{
  "mcpServers": {
    "email-verifier": {
      "command": "npx",
      "args": ["-y", "@apify/mcp-client"],
      "env": {
        "APIFY_TOKEN": "your-token-here",
        "MCP_ACTOR_URL": "https://mcp.apify.com/?tools=themineworks/email-verifier-validator"
      }
    }
  }
}

Then in a Claude conversation:

“Verify these 50 leads from the CSV I uploaded. Remove any invalid or risky addresses and give me a clean list.”

Claude calls the actor, reads the per-address verdicts, and returns only the valid rows with a brief explanation of what was removed.

Filtering Strategy

Not all non-valid results deserve the same treatment:

StatusAction
validSend
invalidRemove permanently — hard bounces guaranteed
risky (disposable)Remove — throwaway accounts, zero intent
risky (role-based)Keep for B2B sequences targeting teams; remove for personal outreach
risky (catch-all)Suppressed send — move to a separate low-priority drip
unknownTest with a small send first; monitor bounce rate

The catch-all split matters most at scale. Google Workspace and Microsoft 365 both run catch-all configurations by default on many business domains, which means a significant percentage of real corporate addresses will return risky (catch-all). Discarding them entirely wastes good leads — the better approach is a lower-volume, higher-spacing send cadence to that segment.

Cost

Each verified address costs $0.002. A list of 10,000 addresses costs $20 — compared to $49–99/month for most SaaS verification tools.

Addresses that fail syntax checking are never charged. The actor stops processing and exits cleanly on empty input.

Frequently Asked Questions

Why does SMTP say “unknown” for addresses I know are real?

Port 25 is firewalled by most cloud providers, including Apify’s compute nodes. When the TCP connection is refused or times out, the actor cannot run the handshake — it returns unknown rather than guessing. The DNS MX result is still valid: you know the domain accepts mail, you just cannot confirm the specific mailbox without SMTP.

Does it handle subaddresses (user+tag@domain.com)?

Yes. The + character is valid in the RFC 5322 local part and passes syntax validation. The MX lookup uses the domain, which is the same regardless of the subaddress. Whether the specific +tag variant is accepted depends on the mail server’s configuration.

Can I run it on a list of 100,000 addresses?

Yes. Set emails to the full list. The actor processes them sequentially with a small jitter between SMTP checks to avoid hammering a single MX server. For very large lists, split into batches of 5,000–10,000 and run in parallel Apify runs.

Related Actor

Explore the scraper referenced in this article — see inputs, outputs, and pricing, then run it on Apify.