Integrate email validation into your application with a simple REST API. Validate single addresses in real time or process entire lists in bulk.
The same validation runs over the Model Context Protocol, so Claude, Cursor, or any MCP client can call it directly —
OAuth sign-in, no API key. Point your client at
https://mcp.mailrook.com/mcp.
Validate an email address with a single request:
All API requests require a Bearer token in the Authorization header. Generate your API key from the dashboard after signing up.
/v1/validate/{email}
Validates one email address and returns detailed verification results. Typical response time is under 500ms.
email
The email address to validate (e.g. [email protected])
include
Comma-separated list of extra data to include. Currently supported: enrichment. When provided, an enrichment object is added to the response containing website, tech stack, company, DNS, and WHOIS data.
{
"data": {
"id": "673603dc-42da-474c-98cf-531ce11b1bf2",
"email": "[email protected]",
"normalized_email": "[email protected]",
"username": "elon",
"domain": "x.com",
"mx_record": "aspmx.l.google.com",
"provider": "google_workspace",
"score": 80,
"isv_format": true,
"isv_domain": true,
"isv_deliverable": true,
"isv_nocatchall": false,
"isv_nogeneric": true,
"isv_nodisposable": true,
"isv_nofreeemail": false,
"isv_nouniversity": true,
"result": "risky",
"reason": "catch_all",
"reason_label": "Catch-all domain (accepts all emails)",
"email_security": [],
"protection_level": "basic",
"domain_info": {
"has_spf": true,
"has_dmarc": true,
"has_website": true,
"domain_age_days": 8450,
"domain_created_at": "2003-03-17T00:00:00+00:00",
"domain_expires_at": "2026-03-17T00:00:00+00:00",
"registrar": "CSC Corporate Domains, Inc.",
"registrant_org": "X Corp.",
"registrant_country": "US",
"ns_hosts": [
"ns1.p09.dynect.net",
"ns2.p09.dynect.net"
],
"a_record_ip": "104.244.42.193",
"asn": "AS13414",
"asn_org": "Twitter Inc.",
"email_security": [],
"protection_level": "basic"
}
},
"code": 0,
"message": "ok"
}
{
"data": {
"id": "673603dc-42da-474c-98cf-531ce11b1bf2",
"email": "[email protected]",
"normalized_email": "[email protected]",
"username": "elon",
"domain": "x.com",
"mx_record": "aspmx.l.google.com",
"provider": "google_workspace",
"score": 80,
"isv_format": true,
"isv_domain": true,
"isv_deliverable": true,
"isv_nocatchall": false,
"isv_nogeneric": true,
"isv_nodisposable": true,
"isv_nofreeemail": false,
"isv_nouniversity": true,
"result": "risky",
"reason": "catch_all",
"reason_label": "Catch-all domain (accepts all emails)",
"email_security": [],
"protection_level": "basic",
"domain_info": {
"has_spf": true,
"has_dmarc": true,
"has_website": true,
"domain_age_days": 8450,
"domain_created_at": "2003-03-17T00:00:00+00:00",
"domain_expires_at": "2026-03-17T00:00:00+00:00",
"registrar": "CSC Corporate Domains, Inc.",
"registrant_org": "X Corp.",
"registrant_country": "US",
"ns_hosts": [
"ns1.p09.dynect.net",
"ns2.p09.dynect.net"
],
"a_record_ip": "104.244.42.193",
"asn": "AS13414",
"asn_org": "Twitter Inc.",
"email_security": [],
"protection_level": "basic"
},
"enrichment": {
"domain": "example.com",
"website": {
"url": "https://example.com",
"title": "Example Domain",
"is_reachable": true,
"is_crawl_blocked": false
},
"tech_stack": {
"cms": "wordpress",
"ecommerce": null,
"pixels": [
"meta_pixel"
],
"analytics": [
"google_analytics"
],
"marketing": [],
"chat": [],
"affiliate": [],
"email_security": [
"proofpoint"
]
},
"social_links": {
"twitter": "example",
"linkedin": "company/example",
"github": "example"
},
"company": {
"name": "Example Inc.",
"industry": "Technology",
"size": "51-200",
"type": "Private",
"founded": 2010,
"city": "San Francisco",
"state": "CA",
"country_code": "US"
},
"dns": {
"has_spf": true,
"has_dmarc": true,
"ns_hosts": [
"ns1.example.com"
],
"a_record_ip": "93.184.216.34",
"asn": "AS15133",
"asn_org": "Edgecast Inc."
},
"whois": {
"registrar": "RESERVED-Internet Assigned Numbers Authority",
"registrant_org": "IANA",
"registrant_country": "US",
"domain_created_at": "1995-08-14T00:00:00+00:00",
"domain_expires_at": "2025-08-13T00:00:00+00:00",
"domain_age_days": 10818
}
}
},
"code": 0,
"message": "ok"
}
/v1/validate/batch
Submit an array of email addresses for bulk validation. Returns a list ID that you can poll for results.
emails
Array of email addresses to validate
{
"data": {
"list_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"emails_count": 2
},
"code": 0,
"message": "ok"
}
/v1/validate/batch/upload
Upload a CSV or TXT file containing one email per line. Maximum file size is 10 MB.
file
A .csv or .txt file with one email address per line (max 10 MB)
{
"data": {
"list_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"emails_count": 150
},
"code": 0,
"message": "ok"
}
/v1/validate/list/{list_id}
Retrieve the results of a batch validation. Returns 202 while processing is in progress and 200 when complete.
list_id
The list ID returned from the batch submit or upload endpoint
{
"data": [],
"code": 0,
"message": "List is still processing: 45 of 100 emails done"
}
{
"data": [
{
"email": "[email protected]",
"isv_format": true,
"isv_domain": true,
"isv_deliverable": true,
"isv_nocatchall": true,
"isv_nogeneric": true,
"isv_nodisposable": true,
"isv_nofreeemail": true,
"isv_nouniversity": true,
"protection_level": "enterprise"
}
],
"code": 0,
"message": "ok"
}
/v1/enrich/{input}
Returns enrichment data for a domain or email address without performing email deliverability validation. Includes website info, tech stack, company data, DNS records, and WHOIS information.
input
An email address (e.g. [email protected]) or domain (e.g. example.com). When an email is provided, the domain is extracted automatically.
{
"data": {
"domain": "example.com",
"website": {
"url": "https://example.com",
"title": "Example Domain",
"is_reachable": true,
"is_crawl_blocked": false
},
"tech_stack": {
"cms": "wordpress",
"ecommerce": null,
"pixels": [
"meta_pixel"
],
"analytics": [
"google_analytics"
],
"marketing": [],
"chat": [],
"affiliate": [],
"email_security": [
"proofpoint"
]
},
"social_links": {
"twitter": "example",
"linkedin": "company/example",
"github": "example"
},
"company": {
"name": "Example Inc.",
"industry": "Technology",
"size": "51-200",
"type": "Private",
"founded": 2010,
"city": "San Francisco",
"state": "CA",
"country_code": "US"
},
"dns": {
"has_spf": true,
"has_dmarc": true,
"ns_hosts": [
"ns1.example.com"
],
"a_record_ip": "93.184.216.34",
"asn": "AS15133",
"asn_org": "Edgecast Inc."
},
"whois": {
"registrar": "RESERVED-Internet Assigned Numbers Authority",
"registrant_org": "IANA",
"registrant_country": "US",
"domain_created_at": "1995-08-14T00:00:00+00:00",
"domain_expires_at": "2025-08-13T00:00:00+00:00",
"domain_age_days": 10818
}
},
"code": 0,
"message": "ok"
}
The enrichment object is returned by the /v1/enrich/{input} endpoint and optionally included in validation responses via ?include=enrichment.
| Field | Type | Description |
|---|---|---|
| domain | string | The domain that was enriched |
| website | ||
| website.url | string|null | Resolved website URL |
| website.title | string|null | Page title from the website |
| website.is_reachable | boolean | Whether the website is reachable |
| website.is_crawl_blocked | boolean | Whether crawling is blocked by the website |
| tech_stack | ||
| tech_stack.cms | string|null | Detected CMS (e.g. wordpress, shopify) |
| tech_stack.ecommerce | string|null | Detected e-commerce platform |
| tech_stack.pixels | array | Tracking pixels detected (e.g. meta_pixel, tiktok_pixel) |
| tech_stack.analytics | array | Analytics tools (e.g. google_analytics, hotjar) |
| tech_stack.marketing | array | Marketing tools (e.g. hubspot, mailchimp) |
| tech_stack.chat | array | Chat/support widgets (e.g. intercom, zendesk) |
| tech_stack.affiliate | array | Affiliate/referral tools |
| tech_stack.email_security | array | Email security providers detected from MX records (e.g. proofpoint, mimecast) |
| social_links | ||
| social_links | object|null | Social media handles found on the website. Keys: twitter, instagram, facebook, youtube, tiktok, linkedin, github. Values are normalized handles/paths (e.g. "acme", "company/acme", "@acme"). |
| company | ||
| company.name | string|null | Company name |
| company.industry | string|null | Industry classification |
| company.size | string|null | Employee count range (e.g. 51-200) |
| company.type | string|null | Company type (e.g. Private, Public) |
| company.founded | integer|null | Year founded |
| company.city | string|null | City |
| company.state | string|null | State or region |
| company.country_code | string|null | ISO 3166-1 alpha-2 country code |
| dns | ||
| dns.has_spf | boolean|null | Domain has an SPF record |
| dns.has_dmarc | boolean|null | Domain has a DMARC record |
| dns.ns_hosts | array|null | Nameserver hostnames |
| dns.a_record_ip | string|null | Primary A record IP address |
| dns.asn | string|null | Autonomous System Number |
| dns.asn_org | string|null | ASN organization name |
| whois | ||
| whois.registrar | string|null | Domain registrar |
| whois.registrant_org | string|null | Registrant organization |
| whois.registrant_country | string|null | Registrant country code |
| whois.domain_created_at | string|null | Domain registration date (ISO 8601) |
| whois.domain_expires_at | string|null | Domain expiration date (ISO 8601) |
| whois.domain_age_days | integer|null | Domain age in days since registration |
Both /v1/enrich/{input} and /v1/validate/{email}?include=enrichment support third-party enrichment providers for deeper data. Pass provider API keys via the X-Enrich-Providers header as a JSON object:
Provider keys are never stored — they are only used for the current request. More providers will be added over time.
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier for this validation |
| string | The email address that was validated | |
| normalized_email | string | Normalized form of the email (dots removed, +tags stripped, alias domains canonicalized) |
| username | string | Local part of the email (before @) |
| domain | string | Domain part of the email (after @) |
| mx_record | string|null | Primary MX record for the domain |
| provider | string|null | Detected email provider (e.g. gmail, google_workspace, microsoft_365, outlook, yahoo, protonmail, icloud, zoho, yandex, mailru, proofpoint, mimecast, aol, fastmail). Null if unknown. |
| score | integer | Overall quality score from 0 to 100 |
| isv_format | boolean | Email has valid syntax (RFC compliant) |
| isv_domain | boolean | Domain exists and has valid DNS records |
| isv_deliverable | boolean|null | Mail server accepts email for this address. Null if unable to verify. |
| isv_nocatchall | boolean|null | Domain is not a catch-all (accepts mail for any address) |
| isv_nogeneric | boolean | Not a generic/role address (e.g. info@, support@) |
| isv_nodisposable | boolean | Not a disposable/temporary email address |
| isv_nofreeemail | boolean | Not from a free email provider (e.g. Gmail, Yahoo) |
| isv_nouniversity | boolean | Not from an educational institution domain |
| result | string | Overall result: deliverable, undeliverable, risky, or unknown |
| reason | string | Reason code: accepted_email, rejected_email, invalid_format, invalid_domain, disposable_email, catch_all, unable_to_verify, sender_blocked |
| reason_label | string|null | Human-readable reason label (e.g. "Mailbox not found"). Null for accepted_email. |
| email_security | array | Email security gateways detected from MX records (e.g. proofpoint, mimecast, barracuda). Empty array if none detected. |
| protection_level | string | Synthesized email protection level: none (no gateway or DMARC enforcement), basic (SPF + strict DMARC policy), or enterprise (dedicated security gateway like Proofpoint or Mimecast). |
| domain_info | object|null | Domain WHOIS and DNS information. Null if domain data is not yet available. |
| domain_info.has_spf | boolean|null | Domain has an SPF record |
| domain_info.has_dmarc | boolean|null | Domain has a DMARC record |
| domain_info.has_website | boolean|null | Domain has a reachable website |
| domain_info.domain_age_days | integer|null | Domain age in days since registration |
| domain_info.domain_created_at | string|null | Domain registration date (ISO 8601) |
| domain_info.domain_expires_at | string|null | Domain expiration date (ISO 8601) |
| domain_info.registrar | string|null | Domain registrar |
| domain_info.registrant_org | string|null | Registrant organization |
| domain_info.registrant_country | string|null | Registrant country code |
| domain_info.ns_hosts | array|null | Nameserver hostnames |
| domain_info.a_record_ip | string|null | Primary A record IP address |
| domain_info.asn | string|null | Autonomous System Number |
| domain_info.asn_org | string|null | ASN organization name |
| domain_info.email_security | array | Email security gateways (same as top-level email_security) |
| domain_info.protection_level | string | Protection level (same as top-level protection_level) |
Calls are metered in credits. Every authenticated response carries your balance, so an integration can pace itself without a second request.
| Operation | Credits | Notes |
|---|---|---|
| GET /v1/validate/{email} | 1 | Per address. |
| GET /v1/validate/{email}?include=enrichment | 3 | The validation plus the enrichment lookup. |
| GET /v1/enrich/{input} | 2 | Per domain. |
| POST /v1/validate/batch | 1 | Per unique address. The list is de-duplicated first, and the whole batch is charged up front — a batch you cannot afford is rejected with 402 and nothing is charged. |
| POST /v1/validate/batch/upload | 1 | Per unique address, charged up front, same as the JSON batch. |
| GET /v1/validate/list/{list_id} | 0 | Reading results is free — the batch was already charged on submission, so polling costs nothing. |
Returned on every authenticated response, including the 402. Credits are spent soonest-to-expire first: the daily allowance, then the plan allowance, then purchased credits.
| Header | Type | Description |
|---|---|---|
| X-Credits-Remaining | integer | Everything spendable right now: today's free allowance, plus whatever the plan has left this period, plus purchased credits. This is the one number to poll — it is what runs out. |
| X-Credits-Free-Today | integer | Of that total, how much is today's free allowance. Resets at midnight UTC. |
| X-Credits-Plan | integer | Of that total, how much of the plan's monthly allowance is left this billing period. Absent on accounts with no plan. |
| X-Credits-Paid | integer | Of that total, the purchased balance. Purchased credits never expire and are spent last. |
| X-Plan | string | Key of the active plan. Absent on accounts with no plan. |
The three bucket headers add up to X-Credits-Remaining; read the total unless you need to know which bucket a call will draw from. Headers for a bucket you do not have are omitted rather than sent as zero, so treat a missing header as "not applicable", not as "empty".
Once your balance cannot cover a call, it returns 402 and nothing is charged. The body says which allowance ran out and when it comes back, so an integration can back off until then rather than retrying into a wall:
{
"data": null,
"code": 402,
"message": "Insufficient credits to validate",
"details": {
"reason": "free_allowance_exhausted",
"plan": null,
"daily_free_allowance": 25,
"free_resets_at": "2026-01-02T00:00:00+00:00",
"hint": "Your free allowance for today is used up and it resets at midnight. A plan gives you a monthly allowance instead.",
"billing_url": "https://mailrook.com/billing"
}
}
| details.reason | Meaning |
|---|---|
| free_allowance_exhausted | No plan on the account and today's free allowance is spent. It comes back at free_resets_at. |
| plan_allowance_exhausted | The plan's allowance for this billing period is spent and there is no purchased balance to fall back on. |
Every response carries the same envelope: data, a numeric code, and a message. On success code is 0; on failure it mirrors the HTTP status, and data is null.
| HTTP Status | Meaning |
|---|---|
| 200 | Success. |
| 201 | Batch accepted. The body carries the list_id to poll. |
| 202 | Batch still processing. The message reports how many of the addresses are done; poll again. |
| 401 | Missing or invalid API key. |
| 402 | Out of credits. See the credits section below — the body says which allowance ran out and when it comes back. |
| 404 | Unknown list id, or no such endpoint. |
| 422 | Invalid email format, unresolvable domain, or a missing required field. |
| 500 | Internal server error. |
API requests are rate-limited per account. If you exceed your limit, you will receive a 429 response. Contact us if you need higher limits for your use case.