Verify Email Lists in Bulk to Cut Your Bounce Rate (2026)
Clean an email list before a campaign with Thirdwatch's Bulk Email Verifier — syntax, MX, disposable, role, free-provider, and SPF/DKIM/DMARC domain checks.

Thirdwatch's Bulk Email Verifier cleans an email list before a campaign using deterministic checks — syntax, MX records, disposable-domain detection, role-address and free-provider flags, and SPF/DKIM/DMARC domain health. It flags deliverability risk (bad syntax, dead domains, throwaways, role inboxes) rather than confirming each mailbox. Built for growth and lifecycle teams cutting bounce rates and protecting sender reputation before a send. No SMTP probing, no API key, transparent pay-per-result.
Why verify an email list before a campaign
A dirty list is the fastest way to wreck sender reputation. Mailbox providers treat hard bounces and spam-trap hits as signals that you don't keep your list clean, and they throttle or junk your future sends accordingly. Google's bulk-sender guidelines are explicit about this: senders are expected to keep their spam-complaint rate low and authenticate with SPF, DKIM, and DMARC, or risk being filtered. According to Validity's 2024 sender-reputation research, inbox-placement rates fall sharply once bounce rates climb past a few percent — a list that bounces 5-10% on the first send can drag deliverability down for weeks, including for the recipients who were reachable.
The job-to-be-done is structured. A growth team has a 30K-contact list assembled from form signups, scraped sources, and a purchased batch, and wants to strip the garbage before the next broadcast. A lifecycle team re-engaging a dormant segment wants to drop dead domains before they trip spam traps. A sales-ops team importing a cold-outreach list wants to flag role inboxes (info@, sales@) and free-provider addresses before a sequence fires. A revops team validating form submissions wants to catch typo'd and throwaway emails at intake. All of these reduce to the same shape: email list in, per-address risk flags out. Cleaning the list first means fewer bounces, a lower spam-complaint rate, and a sender reputation you don't have to rebuild.
How does this compare to the alternatives?
Three common ways to clean an email list before a send:
| Approach | Cost | Reliability | Setup time | Maintenance |
|---|---|---|---|---|
| DIY Python (dnspython + regex) | Free + your engineering time | Brittle; you maintain the disposable list yourself | Hours to days | You own DNS edge cases + blocklist updates |
| Paid SMTP verification SaaS | Subscription or per-email credits | High, with live mailbox probing | Hours (account + integration) | Vendor maintains |
| Thirdwatch Bulk Email Verifier | Transparent pay-per-result | Deterministic checks, production-tested | 5 minutes | Thirdwatch maintains blocklist + checks |
A DIY script gets you syntax and MX quickly, but the disposable blocklist drifts, DKIM-selector probing is fiddly, and you end up maintaining infrastructure that isn't your product. Paid SMTP SaaS adds live mailbox probing the actor deliberately doesn't do — at a subscription cost and with vendor lock-in. The Bulk Email Verifier actor page gives you the deterministic hygiene layer at the lowest unit cost, with the honest trade-off spelled out below: it flags risk, it does not confirm individual mailboxes.
How to verify an email list in 4 steps
Step 1: How do I authenticate against Apify?
Sign in at apify.com (free tier, no credit card), open Settings → Integrations, and copy your personal API token. Every example below assumes the token is in APIFY_TOKEN:
export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"Step 2: How do I verify a batch of emails?
Pass an array of addresses as emails. Keep checkDomainHealth on to get SPF/DKIM/DMARC flags per unique domain. The deep flag is a no-op (SMTP isn't available), so leave it at its default.
import os, requests, pandas as pd
ACTOR = "thirdwatch~email-verifier"
TOKEN = os.environ["APIFY_TOKEN"]
EMAILS = [
"founder@stripe.com",
"info@example.com",
"test@gmail.com",
"hello@mailinator.com",
"not-an-email",
# ... thousands more; cost scales with unique domains, not row count
]
resp = requests.post(
f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
params={"token": TOKEN},
json={"emails": EMAILS, "checkDomainHealth": True},
timeout=900,
)
df = pd.DataFrame(resp.json())
print(f"{len(df)} emails verified")
print(df["status"].value_counts())You can also paste comma- or newline-separated addresses into a single emails entry — the actor splits them for you.
Step 3: How do I keep only the safe-to-send addresses?
Filter on status. A valid row means well-formed syntax, a domain that publishes MX records, and no quality red flags — not a confirmed mailbox. Treat risky as a manual-review bucket and drop invalid entirely.
safe = df[df["status"] == "valid"]
review = df[df["status"] == "risky"]
drop = df[df["status"].isin(["invalid", "unknown"])]
# Tighten further: exclude free-provider and role inboxes from a B2B send
b2b = safe[~safe["isFreeProvider"] & ~safe["isRole"]]
print(f"send to: {len(b2b)}")
print(f"review: {len(review)} (disposable / role / flagged)")
print(f"dropped: {len(drop)} (bad syntax or no MX)")
b2b.to_csv("send_list.csv", index=False)isDisposable, isRole, and isFreeProvider let you build the exact suppression rules your campaign needs — a cold B2B sequence might drop all three, while a newsletter re-engagement might keep free-provider addresses and only suppress disposables.
Step 4: How do I audit sender-domain health?
When checkDomainHealth is enabled, each unique domain carries domainHealth.spf, domainHealth.dkim, and domainHealth.dmarc. These describe the recipient domain's published auth records — useful for spotting domains likely to filter aggressively, and for auditing your own sending domains.
health = pd.json_normalize(df.to_dict("records"))
weak = health[
(health["domainHealth.spf"] == False) |
(health["domainHealth.dmarc"] == False)
][["email", "domainHealth.spf", "domainHealth.dkim", "domainHealth.dmarc"]]
print(f"{len(weak)} addresses on domains missing SPF or DMARC")Remember the caveat: a false on domainHealth.dkim means no key was found at the common selectors probed, not that DKIM is definitively unconfigured.
Sample output
Each input address returns one verification record. The fields mirror the actor's dataset exactly — status is the headline classification, and the boolean flags drive your suppression logic. Two redacted records:
[
{
"email": "info@example.com",
"status": "risky",
"syntaxValid": true,
"mxFound": true,
"mxHost": "mx.example.com",
"isDisposable": false,
"isRole": true,
"isFreeProvider": false,
"domainHealth": { "spf": true, "dkim": false, "dmarc": true },
"smtpCheck": "blocked",
"mailboxExists": "unknown",
"confidence": 55,
"reason": "Role-based address (not a personal mailbox)"
},
{
"email": "hello@mailinator.com",
"status": "risky",
"syntaxValid": true,
"mxFound": true,
"mxHost": "mail.mailinator.com",
"isDisposable": true,
"isRole": false,
"isFreeProvider": false,
"domainHealth": { "spf": true, "dkim": false, "dmarc": false },
"smtpCheck": "blocked",
"mailboxExists": "unknown",
"confidence": 30,
"reason": "Disposable / throwaway domain"
}
]status is the field you act on: invalid (bad syntax or no MX) is undeliverable, risky carries a quality flag, valid is well-formed with a mail-capable domain and no flags. Note smtpCheck is always blocked and mailboxExists always unknown — the actor never connects to a mail server, so it cannot promise any one address will land in an inbox.
Common pitfalls
Three things trip up list-cleaning pipelines. Treating valid as "guaranteed deliverable." It isn't — valid means well-formed plus a mail-capable domain, not a confirmed mailbox. Catch-all domains are indistinguishable from real mailboxes without SMTP, so a valid on a catch-all still can't confirm the person. Over-suppressing on isFreeProvider. For B2C sends, Gmail and Outlook addresses are your audience, not noise — only suppress free-provider addresses for B2B sequences. Stale blocklists in DIY setups. Brand-new throwaway domains appear daily; a hand-maintained list goes stale fast, and transient DNS hiccups can briefly report no MX on a known-good domain. Re-run the few that look wrong before deleting them.
Thirdwatch handles each: the disposable blocklist (~7,500 domains) is maintained for you, DNS lookups are memoized per domain so re-runs are cheap, and the honest unknown/blocked reporting means you never mistake a deterministic check for an SMTP guarantee.
Related use cases
- Extract website contacts for lead generation — build the raw list this actor then cleans
- Scrape Google Maps businesses for lead generation — source local-business leads before verification
- Enrich your CRM with LinkedIn profile data — round out contact records after cleaning
- Guide to scraping business data — the category pillar for prospecting workflows
- All Thirdwatch use-case guides
Frequently asked questions
Does this confirm an email is deliverable?
No. The actor runs deterministic checks — syntax, MX records, disposable, role, free-provider, and domain health — and flags risk. It does not connect to recipient mail servers, so it cannot confirm a specific mailbox exists. Mailbox existence is always reported as 'unknown'.
Why is mailboxExists always 'unknown'?
The actor does not run live SMTP mailbox probing, so it never connects to a recipient's mail server and cannot verify one exact mailbox. It is a deterministic syntax/MX/disposable/role/domain-health verifier, not a mailbox-existence checker. Mailbox-dependent fields stay 'unknown'/'blocked'.
What's the difference between 'risky' and 'invalid'?
Invalid means deterministically undeliverable — bad syntax or no MX records, so the domain cannot receive mail at all. Risky means the domain works but carries a quality flag, such as a disposable domain or a role address like info@, that you probably want to review before sending.
Can I verify thousands of emails cheaply?
Yes. DNS lookups are memoized per domain within a run, so a 1,000-email list spanning 50 unique domains performs roughly 50 MX lookups, not 1,000. Cost scales with the number of unique domains, not the raw email count. Pricing is transparent pay-per-result with volume tiers.
What does the domain health check return?
With checkDomainHealth enabled, the actor runs DNS TXT lookups per unique domain and returns domainHealth.spf, domainHealth.dkim, and domainHealth.dmarc as booleans. DKIM is a best-effort probe of common selectors, so a false there does not prove DKIM is unconfigured — it only means no key was found at the selectors checked.
How does this compare to a paid verification API?
Paid SaaS verifiers run live SMTP probes and bill per email on a subscription or credit model. This actor does deterministic checks only — no SMTP — and bills transparent pay-per-result with no subscription. Use it as a high-quality pre-send filter; pair it with an SMTP-capable relay later if you need mailbox-level confirmation.
Related
100 free credits, no credit card.
About 30 real searches. Add the MCP to Claude or Cursor in two minutes.