Skip to main content
Glama
Email Validator for Agents

Email Validator for Agents

Email Validator for Agents (MX, Disposable, Typo, Risk Score v2)

$0.79 per 1,000 emails ($0.00079 per email). Error rows are free. Optional mailbox check with your own MillionVerifier key: +$0.19 per 1,000 checked emails ($0.00019 each), charged only when MillionVerifier returns a real result. You pay MillionVerifier for its credits directly.

Example: 5,000 signup emails cost 5,000 × $0.00079 = $3.95, plus Apify's $0.00005 Actor-start fee.

This Fairquery Apify Actor validates email addresses without sending any email and without SMTP probing. It checks syntax, DNS/MX, null MX, disposable domains (9,800+ bundled), role accounts, free providers, typos ("gamil.com" → "gmail.com"), the mail provider and SPF/DMARC. Each address gets a valid / risky / invalid / unknown verdict with machine-readable reason codes, plus a 0–100 Risk Score v2.

What's new in v2 (Risk Score v2)

All v1 fields are unchanged. v2 only adds fields:

  • risk_score (0–100, higher = riskier), risk_level (low / medium / high) and risk_reasons

  • mx_provider_class: consumer_webmail, business_suite, security_gateway, hosting_or_forwarding, disposable_service, self_hosted_or_unrecognised or none. mail_provider_id gives the provider (google, microsoft, yahoo, proofpoint, …).

  • catch_all_likelihood: low / medium / high / unknown. This is an estimate from the mail provider type, not a measurement (catch_all_basis: "estimate_from_mx_provider"). See the table below.

  • domain_age_days and domain_registered_at from RDAP. Domains under 30 days old get NEW_DOMAIN. If the age is unknown, these fields are null.

  • disposable_infrastructure: the domain's MX host is shared with known disposable-inbox services. This flags new throwaway domains that are not on the list yet.

  • mailbox_verified: always false unless the optional BYOK MillionVerifier check ran with a live key and returned ok or invalid.

Related MCP server: inboxvalid-mcp

When to use this Actor (LLM / tool selection)

Use it when an agent needs to:

  • Pre-filter a lead / CRM / signup list before outreach, import or paid verification

  • Decide whether an address can receive mail (domain exists, has MX, no null MX)

  • Flag disposable inboxes (including new ones on disposable infrastructure), brand-new domains, role addresses and free webmail

  • Suggest a fix for a mistyped domain

  • See the mail provider, its type, and the DMARC policy

On its own it does not confirm that a specific mailbox exists. That needs an SMTP conversation, and this Actor does none (see the FAQ). If you need mailbox-level results, add your own MillionVerifier key (optional). valid means "well-formed, the domain can receive mail, no risk flags". It does not mean "mailbox confirmed".

How it works

Check

Source

Syntax

Pragmatic RFC 5321/5322 subset; IDN domains → punycode

MX / A / AAAA / TXT

DNS-over-HTTPS https://cloudflare-dns.com/dns-query (cached per domain, 10 domains in parallel)

Disposable domains

Bundled CC0 list from disposable-email-domains (9,800+ domains; subdomains matched)

Disposable infrastructure

88 MX hosts that each served ≥5 listed disposable domains (resolved 2026-10-17), plus any MX host that is itself on the disposable list. Shared general-purpose infrastructure (Google, Microsoft, Cloudflare Email Routing, Mailgun, Amazon SES, registrar forwarding, hosting and parking MX) is excluded, so legitimate domains on those providers are never flagged.

Domain age

RDAP (RFC 9083). Servers come only from the IANA RDAP bootstrap, with a bundled snapshot as fallback. 6 s timeout per request, at most 2 parallel requests per registry, one back-off on HTTP 429, a 120 s budget and 2,000 uncached lookups per run. Failures give null. Some ccTLDs (for example .de) have no RDAP service, so their age is null.

Role / free provider / typo

Built-in lists, plus edit distance against popular providers. The disposable list is checked first: a listed domain (for example yopmail.com, mail.tm or look-alikes such as hotmial.com) is always reported as disposable and never as a typo of a real provider.

Catch-all estimate (prior, not measured)

MX provider

catch_all_likelihood

Basis

Gmail / Google Workspace

low

Unknown recipients are rejected by default; catch-all routing must be switched on by an admin

Microsoft 365 / Outlook.com

medium

Published measurements show RCPT answers that vary from attempt to attempt, and many tenants accept mail and bounce it later

Yahoo / AOL (including custom domains on Yahoo MX)

high

They accept every recipient at RCPT time and bounce later

Proofpoint, Mimecast, Barracuda, Cisco and other security gateways

medium

They often accept for the whole domain; this depends on configuration

Disposable-inbox services

high

They accept any local part by design

Everything else (self-hosted, Zoho, iCloud, Proton, hosting, forwarding…)

unknown

No evidence either way

Risk score v2 (additive, capped 0–100)

Signal

Points

invalid verdict (bad syntax, no domain, null MX, no mail server)

100 (score fixed)

Disposable domain (DISPOSABLE_DOMAIN)

+70

Disposable infrastructure, not listed (DISPOSABLE_INFRASTRUCTURE)

+60

Possible typo (POSSIBLE_TYPO)

+45

Domain under 30 days old (NEW_DOMAIN) / under 180 days (YOUNG_DOMAIN)

+35 / +10

DNS not checked (DNS_NOT_CHECKED)

+25

A record only, no MX (IMPLICIT_MX_ONLY)

+20

Catch-all estimate high / medium / unknown

+15 / +8 / +5

Role account (ROLE_ACCOUNT)

+10

No SPF / no DMARC (custom domains only)

+5 / +3

BYOK result catch_all / disposable / deliverable (live key only)

+10 / +60 / −15

risk_level: low < 25 ≤ medium < 60 ≤ high. Treat this score as a pre-send filter, not as proof of deliverability. The verdict and reasons fields work exactly as in v1. The new signals only change them when the BYOK check runs: an invalid result from MillionVerifier makes the verdict invalid, and catch_all or disposable make it risky.

Input

Field

Type

Default

Description

emails

string[]

– (required; sample addresses are only prefilled in the Console form)

Addresses. An entry may be a comma, semicolon or newline separated list, or Name <addr>

checkDns

bool

true

MX/A lookups (turn off for syntax-only checks)

checkSpfDmarc

bool

true

SPF/DMARC presence and DMARC policy

allowRoleAccounts

bool

true

If false, role addresses → risky

allowDisposable

bool

false

If true, disposable domains are not risky

allowFreeProviders

bool

true

If false, free webmail → risky (B2B lists)

maxEmails

int

10000

Safety cap per run (1–100,000)

checkDomainAge

bool

true

RDAP domain age (fails soft to null)

cacheDomainAge

bool

true

Keep RDAP registration dates for 30 days in a named key-value store, fairquery-email-rdap-cache, in your account. It holds domain names and dates only, never email addresses. Turn this off to keep nothing between runs.

millionVerifierApiKey

secret string

–

Optional. Your own MillionVerifier API key (BYOK).

mailboxCheckMaxPerRun

int

1000

Hard cap on MillionVerifier calls per run

{ "emails": ["jane@acme.com", "info@apify.com", "bob@gamil.com"], "allowFreeProviders": false }

Output

Every row has schema_version (1.1.0; v1 rows were 1.0.0, and v2 only adds fields), type (email | error), source_url (the DoH MX query URL) and checked_at.

Real row from beta run TzHumKQWvIqfKfdRm (2026-10-17):

{
  "type": "email", "input": "someone@yahoo.com", "email": "someone@yahoo.com", "domain": "yahoo.com",
  "verdict": "valid", "reasons": ["FREE_PROVIDER"],
  "syntax_valid": true, "is_disposable": false, "is_role_account": false, "is_free_provider": true,
  "did_you_mean": null, "domain_exists": true, "has_mx": true, "null_mx": false, "implicit_mx": false,
  "mx_records": [{ "priority": 1, "exchange": "mta5.am0.yahoodns.net" }, …], "mail_provider": "Yahoo",
  "has_spf": true, "has_dmarc": true, "dmarc_policy": "reject",
  "risk_score": 15, "risk_level": "low", "risk_reasons": ["CATCH_ALL_LIKELY_ESTIMATE"], "risk_score_version": 2,
  "mail_provider_id": "yahoo", "mx_provider_class": "consumer_webmail",
  "catch_all_likelihood": "high", "catch_all_basis": "estimate_from_mx_provider",
  "domain_registered_at": "1995-01-18T05:00:00.000Z", "domain_age_days": 11595,
  "domain_age_source": "https://rdap.verisign.com/com/v1/domain/yahoo.com",
  "disposable_infrastructure": false, "disposable_infrastructure_mx": null,
  "mailbox_verified": false, "mailbox_check": "not_requested", "mailbox_status": null,
  "mailbox_provider": null, "mailbox_raw": null, "mailbox_error": null
}

v1 reason codes (reasons) are unchanged: INVALID_SYNTAX, DOMAIN_NOT_FOUND, NULL_MX and NO_MAIL_SERVER (→ invalid); DISPOSABLE_DOMAIN, ROLE_ACCOUNT, FREE_PROVIDER, POSSIBLE_TYPO and IMPLICIT_MX_ONLY (→ risky, depending on the allow* flags); DNS_NOT_CHECKED (→ unknown). With BYOK there are also MAILBOX_UNDELIVERABLE, MAILBOX_CATCH_ALL and MAILBOX_DISPOSABLE.

mailbox_check is one of not_requested, checked, skipped_invalid, skipped_disposable, skipped_limit or error. mailbox_status is one of deliverable, undeliverable, catch_all, disposable or unknown, mapped 1:1 from MillionVerifier's result field (ok, invalid, catch_all, disposable, unknown). mailbox_raw keeps result, resultcode, subresult, quality and livemode. It never contains your key.

Error rows (machine-readable, never charged)

{ "type": "error", "code": "DNS_LOOKUP_FAILED", "message": "DoH MX acme.com -> HTTP 503", "retryable": true, "input": "jane@acme.com", "domain": "acme.com" }

Codes: INPUT_EMPTY, INPUT_TOO_LARGE, MAX_EMAILS_EXCEEDED, DNS_LOOKUP_FAILED, MAILBOX_KEY_INVALID_FORMAT and MAILBOX_CHECK_STOPPED (for example a wrong key or no credits left). Duplicate addresses (case-insensitive) are skipped and counted in the summary.

Run summary

The key-value store record RUN_SUMMARY has: emails checked, duplicates skipped, verdict counts, domains checked, DNS and RDAP request counts, RDAP cache hits, mailbox checks attempted, billable and failed, charged events, and the echoed input. The API key is never echoed; the summary shows only mailboxCheckEnabled: true/false.

Pricing

Event

Price

When

email-checked

$0.00079

Each email row with a verdict (valid, risky, invalid or unknown). Risk Score v2 is included.

mailbox-checked-byok

$0.00019

Only with your own MillionVerifier key, and only when MillionVerifier returns a real (live-key) ok, invalid, catch_all or disposable result for the row

apify-actor-start

$0.00005

Once per run, per GB of memory (Apify default event)

These are never charged:

  • error rows

  • skipped duplicates

  • rows skipped before the mailbox check (already invalid, disposable, or over mailboxCheckMaxPerRun)

  • MillionVerifier unknown / unverified results

  • vendor errors, timeouts and rate limits

  • demo keys (API_KEY_FOR_*, livemode: false)

Malformed addresses still get an invalid verdict row, and that row is charged like any other check.

Worked examples

  • 5,000 addresses with 200 duplicates and 30 DNS failures: 4,770 × $0.00079 = $3.77, plus $0.00005.

  • 5,000 addresses with BYOK, where 3,600 survivors get a real MillionVerifier result: 5,000 × $0.00079 + 3,600 × $0.00019 = $3.95 + $0.68 = $4.63, plus your MillionVerifier credits.

To cap spend, set a maximum cost per run in the run options; the Actor stops cleanly at the limit and does not start mailbox checks it could not charge for. maxEmails and mailboxCheckMaxPerRun cap each run.

Price comparison (Apify Store, checked 2026-10-17)

Prices are per email, read from each Actor's pricing through the Apify API (GET /v2/acts/{id}) on 2026-10-17. Tier prices depend on your Apify plan. Those Actors verify mailboxes over SMTP via their own off-Apify backends; this Actor does not. The comparison is meant for pre-filtering, not as a claim of equal accuracy.

Tool

What it does

FREE tier

BRONZE

GOLD

Source

This Actor (Risk Score v2)

DNS/list risk score, no SMTP

$0.00079

$0.00079

$0.00079

Store

This Actor + BYOK MillionVerifier

Mailbox check through your own MillionVerifier account

$0.00079 + $0.00019 + your MillionVerifier credits

same

same

MillionVerifier: 50,000 credits for $89 ($0.00178/credit) or 1M for $449 (millionverifier.com)

bounceverify/bounceverify-email-verifier

SMTP verification (own backend)

$0.00089

$0.0008

$0.00065

Store

sumitr_mardy/email-verifier

DNS/list checks; SMTP only through your own relay

$0.00088

–

$0.00055

Store

blessiticus/email-verifier-pro

SMTP verification (external backend)

$0.00085 + $0.00005 start

–

$0.0006

Store

michael.g/email-verifier-validator

Verification (method not disclosed)

$0.10

$0.001

$0.0006

Store

fatihtahta/email-verifier-validator-pro

Resells the validatedmails.com API

$0.002

–

$0.00089

Store

"–" means the tier was not read for this table. We are not the cheapest mailbox verifier on higher Apify tiers, and we do not claim to be. The positioning is: a $0.79 per 1,000 pre-send risk score; send only the survivors to a mailbox verifier.

Optional mailbox check: bring your own MillionVerifier key

  • How: put your key from https://app.millionverifier.com/api into millionVerifierApiKey. Only rows that are not already invalid or disposable are sent, which saves your credits. A free pre-flight call to MillionVerifier's credits endpoint stops the phase at once if the key is wrong or the account has no credits.

  • API used: MillionVerifier Single API v3, GET https://api.millionverifier.com/api/v3/?api=…&email=…&timeout=10 (API docs, OpenAPI 3.1.0, checked 2026-10-17). We use a 15 s request timeout, at most 10 parallel requests and 2 per recipient domain, back-off and retry on HTTP 429/503, and the hard mailboxCheckMaxPerRun cap. Timeouts are not retried, because the vendor may already have used a credit.

  • Vendor limits and billing (from MillionVerifier's help center, checked 2026-10-17):

    • The Single API is limited to 160 requests per second per account.

    • MillionVerifier does not deduct credits for risky (unknown, catch_all) results.

    • It does deduct credits again if the same address is verified again.

    • Credits used appear in your account in one-minute batches.

    • Source: Real-Time API.

    • Within one run, each address is checked once (duplicates are removed first). Nothing is cached across runs.

  • Terms (checked 2026-10-17):

    • MillionVerifier explicitly allows integrating its API into other products and reselling it (Resellers; the API docs say "You may also use our API to resell our services").

    • Its Terms of Service (published 20 July 2026) treat use through your API key or an integration as your use. You must protect your key and use the data lawfully.

    • The service is for business use only.

    • With BYOK you use your own account, so nothing is resold.

  • Your key:

    • It is a secret input, encrypted by Apify.

    • The Actor never logs it.

    • It is never written to the dataset, the key-value store or RUN_SUMMARY.

    • Error messages contain only our own codes, never the key.

  • Privacy: with BYOK, the email addresses you submit are sent to MillionVerifier, which acts as your processor under its DPA. Without a key, no full address leaves the Actor.

  • Demo keys: MillionVerifier's documented test keys (for example API_KEY_FOR_OK) return fake results with livemode: false. They never change the verdict, never set mailbox_verified, and are never charged.

  • "Deliverable" does not mean inbox placement.

Memory / runtime

Default 128 MB. No browser and no proxy. About 1,000 emails across a few hundred domains usually finish in about a minute. The first run on new domains is slower because of RDAP lookups; later runs reuse the 30-day cache.

MCP / Apify tools

Call from agents via Apify MCP (https://mcp.apify.com, authenticated with your Apify token), Actor fairquery/email-validator-mx-disposable:

{ "actor": "fairquery/email-validator-mx-disposable", "input": { "emails": ["jane@acme.com"] } }

Read dataset items (type == "email") and the RUN_SUMMARY record. A standalone stdio MCP server lives in the GitHub repo under mcp/.

Local stdio MCP server

This repo ships a stdio MCP server (TypeScript, @modelcontextprotocol/sdk) under mcp/ exposing validate_emails (runs this Actor through the Apify API) and the free offline check_email_syntax. initialize and tools/list work without a token; tool calls require APIFY_TOKEN (billed per the Store pricing).

cd mcp && npm install && npm run build
node scripts/smoke-test.mjs                          # initialize + tools/list, no token
APIFY_TOKEN=... node scripts/smoke-test.mjs --call   # real tool call
{ "mcpServers": { "fairquery-email": { "command": "node", "args": ["/path/to/email-validator-mx-disposable/mcp/dist/index.js"], "env": { "APIFY_TOKEN": "<your Apify token>" } } } }

Docker: docker build -t fairquery-email-mcp . (the repo-root Dockerfile builds this MCP server and is what Glama uses; the Apify Actor image is built from .actor/Dockerfile). glama.json lists fairquery as maintainer.

Works well after

Troubleshooting

Symptom

Cause and fix

Run fails at once with INPUT_EMPTY

emails was empty. The sample addresses in the Console form are prefill only. They are not sent with API calls, so an empty API call fails fast and costs nothing.

Some rows are DNS_LOOKUP_FAILED errors

The DNS-over-HTTPS lookup failed or timed out (retryable: true). These rows are free; re-run just those addresses.

MAX_EMAILS_EXCEEDED error row

You sent more addresses than maxEmails. Only the first maxEmails were checked; raise the cap or split the list.

domain_age_days is null

The TLD has no RDAP service in the IANA bootstrap (for example .de), or the registry timed out or rate-limited the lookup. The score just leaves the age out.

MAILBOX_CHECK_STOPPED with MV_INVALID_API_KEY / MV_INSUFFICIENT_CREDITS

Check your key or your MillionVerifier credits. Rows without a mailbox result were not charged the BYOK event.

A valid address bounced

valid means the address is well-formed, the domain receives mail, and there are no risk flags. Without BYOK it does not confirm that the mailbox exists.

Free webmail marked risky

allowFreeProviders is false. Set it to true for B2C lists.

FAQ

Why no SMTP probing?

Three reasons.

  1. It is not possible from Apify. We tested it on 2026-10-17 with a private, throwaway test Actor (run OYO4B7Rqdl3lGAtJv, 128 MB, 33 s):

    • TCP port 25 to Gmail, Outlook and Yahoo MX hosts timed out. Ports 587 and 465 timed out too.

    • Port 443 connected in 4 ms.

    • Apify Proxy refused CONNECT host:25 on both the datacenter group (590 UPSTREAM502) and the residential group (590 UPSTREAM453).

    • The egress IP was in AWS address space, and AWS blocks outbound port 25 by default.

    • The probe only opened TCP connections and sent QUIT. It never sent RCPT TO, and it has since been deleted.

  2. The workarounds have real costs. You would need your own relay host or a rotating "SMTP proxy" pool, and that creates IP-reputation and blocklist risk. Spamhaus describes unsolicited probing as potentially hostile.

  3. Even with port 25 open, many answers carry little information. Catch-all domains accept every recipient, Yahoo accepts every recipient at RCPT time, Microsoft's answers vary, and greylisting delays answers.

Mailbox-level verifiers on Apify generally run the SMTP step outside Apify. If you need that step, use the optional MillionVerifier BYOK check: the SMTP work happens on MillionVerifier's infrastructure, under your account.

Does it send email or connect to mail servers?

No. It uses DNS-over-HTTPS (MX, A/AAAA, TXT), RDAP (registry web APIs) and bundled lists. There is no SMTP handshake, and nothing is sent to the address. With BYOK, MillionVerifier does its own checks on its infrastructure.

Does valid or a low risk_score mean the mailbox exists?

No. mailbox_verified is false unless the BYOK check returned a live ok or invalid result.

Is catch_all_likelihood measured?

No. It is an estimate from the mail provider type (see the table above). Only the BYOK result catch_all comes from an actual check, done by MillionVerifier.

How much does a run cost?

$0.00079 per email row with a verdict, plus a $0.00005 start fee. 1,000 emails ≈ $0.79. BYOK adds $0.00019 per real MillionVerifier result. Error rows and skipped duplicates are free.

How do I limit spend?

Set a maximum cost per run in the run options, and/or lower maxEmails and mailboxCheckMaxPerRun.

Can an AI agent call it?

Yes. Use Apify MCP with Actor fairquery/email-validator-mx-disposable, or the Fairquery MCP server (npx -y @fairquery/agent-trust-mcp). The server's free email_check_syntax tool runs locally; its email_validate_deliverability tool calls this Actor.

Is my list stored?

Results are written only to your run's dataset and RUN_SUMMARY in your Apify account. Two things are sent elsewhere:

  • Domain names (not full addresses) go to the Cloudflare DNS-over-HTTPS resolver and to the registry RDAP servers.

  • If cacheDomainAge is on, registration dates for domains (no email addresses) are kept for 30 days in the fairquery-email-rdap-cache store in your account.

Full addresses leave the Actor only when you add a MillionVerifier key.

Fairquery family

Other Fairquery tools on the Apify Store. Each one returns structured rows with machine-readable error codes, and error rows are never charged:

All Fairquery Actors: https://apify.com/fairquery

Kits & templates

Optional guides from Fairquery for wiring this Actor into agents and automations (the Actor works fine without them):

Disclaimer

DNS- and registry-based validation cannot guarantee that a mailbox exists or accepts mail. Use the results to reduce bounces, not as proof of deliverability. Comply with anti-spam law (CAN-SPAM, GDPR/ePrivacy) when you send email. MillionVerifier is a third-party service under its own terms; Fairquery is not affiliated with it.

Development

Local development, tests and deployment notes are in DEVELOPMENT.md. The Apify Store README is .actor/README.md.

Available Tools

2 tools
check_email_syntaxCheck email syntax offline (free)A
Read-onlyIdempotent
Inspect

WHAT: Check whether strings are syntactically valid email addresses and normalize them (extracts "Name ", lowercases the domain), entirely locally. WHEN TO USE: fast pre-filtering or parsing before calling validate_emails, or when only the format matters. WHEN NOT TO USE: it does not check that the domain exists, has MX records, is disposable, a role account or a typo - use validate_emails (paid) for deliverability. COST: free; pure local function, no network, no APIFY_TOKEN. BEHAVIOR: synchronous and CPU-bound, no rate limit; 10,000 addresses return in well under a second. RETURNS: one "email_syntax" row per input element; reason is null or INVALID_SYNTAX. PARAMETERS: emails: each element is checked on its own and returns exactly one row, in input order (no splitting of comma/semicolon lists, no deduplication - unlike validate_emails). Normalization before checking: surrounding whitespace is trimmed, a trailing "Name " is reduced to addr, and the domain is lowercased (the local part keeps its case). Syntax rules: total <= 254 chars, local part <= 64 chars with no leading/trailing or double dot, domain labels of letters/digits/hyphens and a 2-63 letter TLD; quoted local parts, IP-literal domains and non-ASCII (IDN) addresses are reported as INVALID_SYNTAX here. input always echoes the original string so you can map rows back. EXAMPLE: {"emails":["Jane jane@Example.com"]} -> email "jane@example.com", syntax_valid true, domain "example.com".

NameRequiredDescriptionDefault
emailsYesRequired. 1-10000 strings, one address per element; bare "jane@acme.com" or "Name <addr>" form (one row per element, no list splitting). Example: ["Jane <jane@Example.com>","not-an-email"]

Output Schema

NameRequiredDescription
countYesNumber of rows returned
itemsYesResult rows; each has a "type" field (data rows or machine-readable "error" rows)

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive/closed-world, and the description adds what they cannot: free, purely local, no APIFY_TOKEN, synchronous and CPU-bound, no rate limit, and a performance guarantee (10k addresses in well under a second). It also discloses the return shape and the INVALID_SYNTAX reason value.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Strongly front-loaded with labeled sections (WHAT, WHEN, COST, BEHAVIOR, RETURNS, PARAMETERS, EXAMPLE) so it scans well, and no sentence is filler. It is nonetheless on the verbose end for a single-parameter tool, with the PARAMETERS block restating normalization details at length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a one-param, locally-executed tool with an output schema already present, the definition covers cost, performance, failure mode, normalization, ordering guarantees, and a worked example. An agent has everything needed to call it correctly and to interpret rows back to inputs.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Goes far beyond the 100% schema coverage: per-element row semantics, no comma/semicolon splitting or deduplication (contrasted with validate_emails), the exact normalization order (trim, unwrap "Name <addr>", lowercase domain only), and concrete syntax limits (254 total, 64 local, 2-63 letter TLD, IDN/quoted/IP-literal rejected). Behavior an agent could not derive from the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The WHAT clause gives a specific verb and resource (check and normalize email address syntax) and immediately scopes it to local-only operation, which cleanly separates it from validate_emails. An agent can route between the two tools without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit WHEN TO USE (fast pre-filtering before validate_emails, or when only format matters) and WHEN NOT TO USE (no MX, deliverability, disposable/role/typo checks - use validate_emails instead) with the alternative named. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_emailsValidate email addresses (syntax, MX, disposable, typo)A
Read-onlyIdempotent
Inspect

WHAT: Validate email addresses for deliverability risk: RFC-style syntax, domain existence and MX records (DNS-over-HTTPS), null MX, disposable/temporary inbox domains, role accounts (info@, sales@), free webmail providers, common domain typos with a "did you mean" suggestion, and optional SPF/DMARC presence and policy. WHEN TO USE: cleaning signup or lead lists, checking an address before sending, flagging throwaway or typo signups. WHEN NOT TO USE: for a quick offline format check or parsing "Name " strings use check_email_syntax (free). Does NOT send email or do SMTP mailbox probing, so it cannot confirm that a specific mailbox exists. BEHAVIOR: runs the Fairquery Apify Actor fairquery/email-validator-mx-disposable via the Apify API (DNS lookups via Cloudflare DoH). Requires APIFY_TOKEN. Duplicates (case-insensitive) are skipped. Typical run 3-30 s. COST: paid (billed to your Apify account, pay-per-event): $0.00005 per run + $0.00079 per email row with a verdict; error rows are free. 100 emails ~ $0.08. RETURNS: one email row per unique address; verdict follows reasons[]: INVALID_SYNTAX, DOMAIN_NOT_FOUND, NULL_MX, NO_MAIL_SERVER -> invalid; DISPOSABLE_DOMAIN, ROLE_ACCOUNT, FREE_PROVIDER (per allow* flags), POSSIBLE_TYPO, IMPLICIT_MX_ONLY -> risky; DNS_NOT_CHECKED -> unknown. Error rows are free and never sit next to an email row for the same address: INPUT_EMPTY (nothing to check), MAX_EMAILS_EXCEEDED (once, for the overflow), DNS_LOOKUP_FAILED (one per address whose domain lookup failed, instead of its email row; retryable=true for transient DNS errors, so resend only those addresses). PARAMETERS: emails: entries are split on commas, semicolons and newlines, trimmed, reduced from "Name " to addr, and case-insensitive duplicates are skipped before checking, so a pasted list in one element works and you pay once per unique address; maxEmails caps that unique count (extras get one free MAX_EMAILS_EXCEEDED row), which makes it your spend cap. checkDns=false skips all DNS lookups: domain_exists / has_mx stay null and an address that is otherwise clean gets verdict "unknown" (DNS_NOT_CHECKED) instead of "valid" - it is still billed. checkSpfDmarc only adds has_spf / has_dmarc / dmarc_policy and never changes the verdict; it is ignored when checkDns=false. The three allow* flags only decide whether a condition downgrades an otherwise valid address to "risky": the reason code (ROLE_ACCOUNT, DISPOSABLE_DOMAIN, FREE_PROVIDER) is listed in reasons[] either way, and no flag can rescue an "invalid" address (bad syntax, DOMAIN_NOT_FOUND, NULL_MX, NO_MAIL_SERVER). POSSIBLE_TYPO and IMPLICIT_MX_ONLY are always "risky". Presets: B2B lead list = allowFreeProviders false + allowRoleAccounts false; consumer signups = defaults. EXAMPLE: {"emails":["someone@gamil.com"]} -> verdict "risky", reasons ["POSSIBLE_TYPO"], did_you_mean "someone@gmail.com".

NameRequiredDescriptionDefault
emailsYesRequired. 1-10000 email addresses to validate. Entries may also be comma/semicolon/newline-separated lists or "Name <addr>" forms. Example: ["john.doe@gmail.com","someone@gamil.com"]
checkDnsNoOptional boolean, default true. true looks up MX/A records (DNS-over-HTTPS) to confirm the domain can receive mail; false = syntax/list checks only, clean addresses then get verdict "unknown". Example: false
maxEmailsNoOptional integer 1-100000, default 10000. Safety/spend cap on unique emails per run; addresses beyond it are skipped with one free MAX_EMAILS_EXCEEDED error row. Example: 500
checkSpfDmarcNoOptional boolean, default true. true adds has_spf, has_dmarc and dmarc_policy (none|quarantine|reject) per domain; never changes the verdict; needs checkDns=true
allowDisposableNoOptional boolean, default false = disposable/temporary-inbox domains (mailinator.com, ...) get "risky"; true stops penalising them (DISPOSABLE_DOMAIN still reported)
allowRoleAccountsNoOptional boolean, default true. false downgrades role mailboxes (info@, sales@, support@, ...; +tags ignored) from "valid" to "risky"; ROLE_ACCOUNT is reported in reasons either way
allowFreeProvidersNoOptional boolean, default true. false downgrades free webmail (gmail.com, outlook.com, yahoo.com, ...) to "risky" - recommended for B2B lead lists

Output Schema

NameRequiredDescription
countYesNumber of rows returned
itemsYesResult rows; each has a "type" field (data rows or machine-readable "error" rows)

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover only the safety profile (readOnly, idempotent, openWorld, non-destructive), leaving the description to carry the operational burden, which it does: APIFY_TOKEN requirement, external Actor invocation, case-insensitive dedup and skip behavior, 3-30s runtime, paid pay-per-event pricing model, and the fact that some checks are billed even when skipped. It also flags what can never be confirmed (mailbox existence) and which error rows are retryable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The headed WHAT/WHEN/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE layout is front-loaded and scannable, and for a 7-parameter tool with billing, cost, and verdict-tier complexity the length is largely earned. It is nonetheless verbose and the RETURNS and PARAMETERS sections restate overlapping verdict/flag behavior, so it is not maximally tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even though an output schema exists and return fields need not be documented, the description still maps verdict values to reason codes and explains which error rows replace email rows and why, which is exactly what an agent needs to interpret partial failures. Everything needed to call the tool correctly and read its results is present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, yet the description still adds meaning the schema cannot express: allow* flags only downgrade an otherwise valid address to 'risky' and never rescue an 'invalid' one, the reason code is reported either way, checkDns=false still bills, checkSpfDmarc never alters the verdict and is ignored when checkDns=false, and maxEmails functions as the spend cap. It also supplies named presets (B2B vs consumer), which are not encoded anywhere in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The WHAT section names a specific verb (validate) and resource (email addresses) and enumerates exactly which checks run: RFC syntax, MX/DNS-over-HTTPS, null MX, disposable domains, role accounts, free providers, typos, SPF/DMARC. It also draws an explicit boundary against the sibling check_email_syntax, so an agent can discriminate without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

WHEN TO USE gives concrete scenarios (cleaning signup/lead lists, pre-send checks, throwaway/typo signups) and WHEN NOT TO USE names the exact alternative (check_email_syntax, free) plus its capability limits (no SMTP mailbox probing, cannot confirm a mailbox exists). Explicit and actionable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updates
    • Changedcheck_email_syntax1 field changed
      • changedInput schema / properties / emails / description
        Previous value: -"Required. 1-10000 strings to check; each may be a bare address or \"Name <addr>\" form. Example: [\"Jane <jane@Example.com>\",\"not-an-email\"]"New value: +"Required. 1-10000 strings, one address per element; bare \"jane@acme.com\" or \"Name <addr>\" form (one row per element, no list splitting). Example: [\"Jane <jane@Example.com>\",\"not-an-email\"]"
    • Changedvalidate_emails7 fields changed
      • changedInput schema / properties / allowDisposable / description
        Previous value: -"Default false (disposable domains are \"risky\"). Set true to not penalize disposable domains."New value: +"Optional boolean, default false = disposable/temporary-inbox domains (mailinator.com, ...) get \"risky\"; true stops penalising them (DISPOSABLE_DOMAIN still reported)"
      • changedInput schema / properties / allowFreeProviders / description
        Previous value: -"Default true. If false, free webmail (gmail.com, outlook.com...) gets \"risky\" - useful for B2B lead lists."New value: +"Optional boolean, default true. false downgrades free webmail (gmail.com, outlook.com, yahoo.com, ...) to \"risky\" - recommended for B2B lead lists"
      • changedInput schema / properties / allowRoleAccounts / description
        Previous value: -"Default true. If false, role addresses (info@, sales@, support@...) get verdict \"risky\"."New value: +"Optional boolean, default true. false downgrades role mailboxes (info@, sales@, support@, ...; +tags ignored) from \"valid\" to \"risky\"; ROLE_ACCOUNT is reported in reasons either way"
      • changedInput schema / properties / checkDns / description
        Previous value: -"Look up MX/A records to confirm the domain can receive mail (default true). false = syntax/list checks only, verdict may be \"unknown\"."New value: +"Optional boolean, default true. true looks up MX/A records (DNS-over-HTTPS) to confirm the domain can receive mail; false = syntax/list checks only, clean addresses then get verdict \"unknown\". Example: false"
      • changedInput schema / properties / checkSpfDmarc / description
        Previous value: -"Also report SPF and DMARC presence and DMARC policy per domain (default true)"New value: +"Optional boolean, default true. true adds has_spf, has_dmarc and dmarc_policy (none|quarantine|reject) per domain; never changes the verdict; needs checkDns=true"
      • changedInput schema / properties / emails / description
        Previous value: -"Required. 1-10000 email addresses to validate. Entries may also be comma/semicolon/newline-separated lists or \"Name <addr>\" forms. Example: [\"john.doe@gmail.com\",\"someone@gmial.com\"]"New value: +"Required. 1-10000 email addresses to validate. Entries may also be comma/semicolon/newline-separated lists or \"Name <addr>\" forms. Example: [\"john.doe@gmail.com\",\"someone@gamil.com\"]"
      • changedInput schema / properties / maxEmails / description
        Previous value: -"Safety cap on unique emails per run (1-100000, default 10000); extras produce a MAX_EMAILS_EXCEEDED error row"New value: +"Optional integer 1-100000, default 10000. Safety/spend cap on unique emails per run; addresses beyond it are skipped with one free MAX_EMAILS_EXCEEDED error row. Example: 500"
  2. 2 tool updatesv1.0.1
    • Addedcheck_email_syntax
    • Changedvalidate_emails9 fields changed
      • changedInput schema / properties / allowDisposable / description
        Previous value: -"If true, disposable domains are not risky"New value: +"Default false (disposable domains are \"risky\"). Set true to not penalize disposable domains."
      • changedInput schema / properties / allowFreeProviders / description
        Previous value: -"If false, free webmail becomes risky"New value: +"Default true. If false, free webmail (gmail.com, outlook.com...) gets \"risky\" - useful for B2B lead lists."
      • changedInput schema / properties / allowRoleAccounts / description
        Previous value: -"If false, info@/sales@ etc. become risky"New value: +"Default true. If false, role addresses (info@, sales@, support@...) get verdict \"risky\"."
      • changedInput schema / properties / checkDns / description
        Previous value: -"MX/A lookups (default true)"New value: +"Look up MX/A records to confirm the domain can receive mail (default true). false = syntax/list checks only, verdict may be \"unknown\"."
      • changedInput schema / properties / checkSpfDmarc / description
        Previous value: -"Report SPF/DMARC (default true)"New value: +"Also report SPF and DMARC presence and DMARC policy per domain (default true)"
      • changedInput schema / properties / emails / description
        Previous value: -"Email addresses to validate"New value: +"Required. 1-10000 email addresses to validate. Entries may also be comma/semicolon/newline-separated lists or \"Name <addr>\" forms. Example: [\"john.doe@gmail.com\",\"someone@gmial.com\"]"
      • addedInput schema / properties / emails / maxItems
        Added value: +10000
      • addedInput schema / properties / maxEmails / description
        Added value: +"Safety cap on unique emails per run (1-100000, default 10000); extras produce a MAX_EMAILS_EXCEEDED error row"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "count": {
        +      "description": "Number of rows returned",
        +      "type": "integer"
        +    },
        +    "items": {
        +      "description": "Result rows; each has a \"type\" field (data rows or machine-readable \"error\" rows)",
        +      "items": {
        +        "additionalProperties": {},
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "count",
        +    "items"
        +  ],
        +  "type": "object"
        +}
  3. 1 tool updatev0.1.0
    • First observedvalidate_emails

TDQS

A4.8/5.0

Scored across 2 tools

Disambiguation5/5

The two tools occupy clearly separated layers: check_email_syntax is a free, local, format-only check, while validate_emails is the paid network deliverability check. Each description explicitly states when to use the other instead, eliminating misselection risk.

Naming Consistency5/5

Both names follow a consistent snake_case verb_noun pattern (validate_emails, check_email_syntax) with the subject ('emails') clearly identified. No mixed conventions or vague verbs.

Tool Count4/5

Two tools is thin in absolute terms, but for a narrow email-validation purpose the free/paid split is a natural and justified decomposition rather than an under-built surface. Nothing appears to be padded or missing at the tool level.

Completeness4/5

The surface covers both the offline syntax tier and the full deliverability tier (syntax, DNS/MX, disposable, role, typo, SPF/DMARC) with documented verdicts and error rows. The only real gap is that mailbox existence/SMTP probing and any async run-status retrieval are absent, both explicitly acknowledged as out of scope.

Maintenance

ActivityNo data
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Comprehensive email validation MCP server that checks syntax, MX records, disposable domains, role-based accounts, SPF/DKIM, typo suggestions, and risk scoring.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    An MCP server that exposes a verify_email tool for checking email syntax, disposable domains, and MX records, returning a structured validity result.
    1
    ISC
  • A
    license
    B
    quality
    D
    maintenance
    An MCP server that provides email verification, returning valid, invalid, or risky status with detailed checks and metadata. It enables verifying email addresses via a simple tool interface.
    3
    MIT
  • F
    license
    A
    quality
    D
    maintenance
    An MCP server that exposes a mock email verification tool over stdio transport, providing structured JSON results with statuses valid, invalid, or risky.
    1
    -