Verify endpoint

POST /api/v1/verify — validate a single email address and get a score, verdict, and per-check breakdown.

Request

POST https://mailsentry.dev/api/v1/verify
Content-Type: application/json
X-API-Key: ms_live_your_key_here

{
  "email": "user@example.com",
  "depth": "standard"
}
FieldTypeRequiredDescription
emailstringYesThe email address to validate.
depthstringNo"standard" (default) or "extended". Extended mode re-probes SMTP after a 3-second delay to resolve greylisting and confirm catch-all results. Takes 4–8 seconds but gives higher confidence.

Response

Valid email — clean pass

{
  "email": "sarah@stripe.com",
  "score": 95,
  "verdict": "valid",
  "recommendation": "Safe to send",
  "verification_level": "confirmed",
  "verification_depth": "standard",
  "warning": null,
  "checks": {
    "syntax":        { "valid": true, "normalized": "sarah@stripe.com", "plus_alias": null },
    "mx_records":    { "valid": true, "records": ["aspmx.l.google.com"] },
    "disposable":    { "is_disposable": false, "list_updated": null },
    "role_based":    { "is_role_based": false, "role_type": null },
    "free_provider": { "is_free": false, "name": null, "is_business": true, "mx_provider": "Google Workspace" },
    "typo":          { "has_typo": false, "suggestion": null },
    "smtp":          { "valid": true, "catch_all": false, "mx_host": "aspmx.l.google.com", "greylisted": false, "catch_all_confidence": null },
    "gibberish":     { "is_gibberish": false, "score": 100, "pattern": null },
    "spam_trap":     { "is_potential_trap": false, "type": null, "confidence": null },
    "domain_age":    { "domain_created": "2010-01-18", "age_days": 5942, "risk_flag": null },
    "abuse":         { "is_toxic": false, "type": null }
  },
  "response_time_ms": 42
}

Everything checks out — verified mailbox on Google Workspace (mx_provider), domain registered 16 years ago, no flags. The is_business: true field confirms this is a corporate email, not a free provider.

Typo detected — "Did you mean?"

{
  "email": "john@gmial.com",
  "score": 15,
  "verdict": "invalid",
  "recommendation": "Do not send",
  "verification_level": "confirmed",
  "verification_depth": "standard",
  "warning": "Possible typo detected",
  "checks": {
    "syntax":        { "valid": true, "normalized": "john@gmial.com", "plus_alias": null },
    "mx_records":    { "valid": false, "records": [] },
    "disposable":    { "is_disposable": false, "list_updated": null },
    "role_based":    { "is_role_based": false, "role_type": null },
    "free_provider": { "is_free": false, "name": null, "is_business": null, "mx_provider": null },
    "typo":          { "has_typo": true, "suggestion": "john@gmail.com" },
    "smtp":          { "valid": false, "catch_all": false, "mx_host": null, "greylisted": false, "catch_all_confidence": null },
    "gibberish":     { "is_gibberish": false, "score": 100, "pattern": null },
    "spam_trap":     { "is_potential_trap": false, "type": null, "confidence": null },
    "domain_age":    { "domain_created": null, "age_days": null, "risk_flag": null },
    "abuse":         { "is_toxic": false, "type": null }
  },
  "response_time_ms": 38
}

The domain doesn't exist, but typo.suggestion tells you exactly what they meant. Use this to prompt "Did you mean john@gmail.com?" in your signup form.

Stacked risk signals — role-based + new domain

{
  "email": "info@newstartup.io",
  "score": 38,
  "verdict": "risky",
  "recommendation": "High risk — we recommend not sending",
  "verification_level": "inferred",
  "verification_depth": "standard",
  "warning": "Domain is catch-all — mailbox existence could not be confirmed",
  "checks": {
    "syntax":        { "valid": true, "normalized": "info@newstartup.io", "plus_alias": null },
    "mx_records":    { "valid": true, "records": ["mx.zoho.com"] },
    "disposable":    { "is_disposable": false, "list_updated": null },
    "role_based":    { "is_role_based": true, "role_type": "info" },
    "free_provider": { "is_free": false, "name": null, "is_business": true, "mx_provider": "Zoho Mail" },
    "typo":          { "has_typo": false, "suggestion": null },
    "smtp":          { "valid": null, "catch_all": true, "mx_host": "mx.zoho.com", "greylisted": false, "catch_all_confidence": 0.17 },
    "gibberish":     { "is_gibberish": false, "score": 100, "pattern": null },
    "spam_trap":     { "is_potential_trap": false, "type": null, "confidence": null },
    "domain_age":    { "domain_created": "2026-04-01", "age_days": 25, "risk_flag": "newly_registered" },
    "abuse":         { "is_toxic": false, "type": null }
  },
  "response_time_ms": 1240
}

Four signals stack: role_type: "info" (generic address), catch_all: true with catch_all_confidence: 0.17 (the local part "info" isn't a real person), risk_flag: "newly_registered" (25 days old), and Zoho Mail hosting. Each field gives developers data to build their own threshold logic.

Response fields

FieldTypeDescription
emailstringThe email address that was verified.
scoreintegerQuality score from 0 (worst) to 100 (best). See Quality scores.
verdictstringOne of valid, caution, risky, invalid.
recommendationstringHuman-readable action: "Safe to send", "Send with caution", etc.
verification_levelstringconfirmed (SMTP verified), inferred (catch-all), or estimated (SMTP inconclusive).
verification_depthstringstandard or extended. Extended re-probes SMTP to resolve greylisting and confirm catch-all.
warningstring | nullOptional advisory, e.g. "Possible typo detected".
checksobjectPer-layer results from all 11 validation layers. See below.
response_time_msintegerServer-side processing time in milliseconds.

checks object — per-layer fields

LayerFieldTypeDescription
syntaxvalidbooleanWhether the email passes RFC 5322 syntax validation.
normalizedstring | nullThe email with IDN domains converted to punycode. null if syntax is invalid.
plus_aliasstring | nullThe plus-alias tag if present (e.g. "newsletter" for user+newsletter@).
disposableis_disposablebooleanWhether the domain is a known disposable email provider.
list_updatedstring | nullDate the disposable domain list was last updated (ISO date). Only present when is_disposable is true.
role_basedis_role_basedbooleanWhether the local part is a generic role (info@, admin@, sales@).
role_typestring | nullThe matched role prefix (e.g. "info", "admin", "sales").
free_provideris_freebooleanWhether the domain is a free email provider (Gmail, Yahoo, etc.).
namestring | nullProvider name if recognized (e.g. "Gmail", "Outlook").
is_businessboolean | nullWhether the domain uses a hosted business email provider. true for custom domains on Google Workspace, Microsoft 365, etc. false for free providers. null if undetermined.
mx_providerstring | nullThe email hosting provider detected from MX records (e.g. "Google Workspace", "Microsoft 365", "Zoho Mail").
smtpvalidboolean | nullMailbox exists (true), doesn't exist (false), or couldn't determine (null).
catch_allbooleanWhether the domain accepts all addresses.
mx_hoststring | nullThe MX server hostname used for the SMTP check.
greylistedbooleanWhether the server returned a temporary rejection (greylisting).
catch_all_confidencenumber | nullWhen catch_all is true, a 0–1 score estimating how likely the specific mailbox exists. Based on local-part analysis — john.smith@ scores higher than xq7z@.
gibberishis_gibberishbooleanWhether the local part looks like random characters.
scoreinteger0–100 naturalness score (100 = looks like a real name).
patternstring | nullWhen gibberish is detected: keyboard_sequence, repeated_chars, numeric_only, consonant_cluster, or random_characters.
spam_trapis_potential_trapbooleanWhether the address matches known spam trap patterns.
typestring | nullhoneypot_pattern, recycled_risk, or trap_domain.
confidencenumber | null0–1 confidence in the trap detection. Pattern-based — not a definitive match.
domain_agedomain_createdstring | nullDomain registration date (ISO format).
age_daysinteger | nullNumber of days since registration.
risk_flagstring | nullnewly_registered (≤30 days) or young_domain (31–90 days).