Batch Validation
Validate up to 100 email addresses in a single request
Authentication
Include your API key in the X-API-Key header:
X-API-Key: ml_pv_Ab3Kx9mN2pQ7rT4wL8yD...The credential must be a Validation product credential with the Api channel enabled.
Validate a Batch
POST https://api.mail.land/v1/validation/batch{
"emails": [
"[email protected]",
"[email protected]",
"invalid@",
"[email protected]",
"[email protected]"
],
"verify_smtp": true
}| Field | Type | Default | Description |
|---|---|---|---|
emails | array | - | Email addresses to validate (required, max 100) |
verify_smtp | bool | true | Perform SMTP mailbox verification |
curl -X POST https://api.mail.land/v1/validation/batch \
-H "X-API-Key: ml_pv_Ab3Kx9mN2pQ7rT4wL8yD..." \
-H "Content-Type: application/json" \
-d '{
"emails": ["[email protected]", "[email protected]", "[email protected]"],
"verify_smtp": true
}'Response
{
"total_count": 5,
"processed_count": 5,
"successful_count": 5,
"credits_consumed": 4,
"free_validations": 1,
"results": [
{
"email": "[email protected]",
"result": "Deliverable",
"score": 95,
"syntax": { "is_valid": true, "has_typo": false },
"disposable": { "is_disposable": false },
"role": { "is_role_account": false },
"mx": { "has_mx": true, "records": ["mx.example.com"], "accepts_mail": true },
"smtp": { "can_connect": true, "is_deliverable": true, "is_catch_all": false, "reachability": "safe" },
"provider": { "name": "Unknown", "type": "unknown" },
"cached": false,
"verified_at": "2026-02-17T12:34:56Z"
},
{
"email": "[email protected]",
"result": "Deliverable",
"score": 95,
"syntax": { "is_valid": true, "has_typo": false },
"disposable": { "is_disposable": false },
"role": { "is_role_account": false },
"mx": { "has_mx": true, "records": ["gmail-smtp-in.l.google.com"], "accepts_mail": true },
"smtp": { "can_connect": true, "is_deliverable": true, "is_catch_all": false, "reachability": "safe" },
"provider": { "name": "Gmail", "type": "consumer" },
"cached": false,
"verified_at": "2026-02-17T12:34:56Z"
},
{
"email": "invalid@",
"result": "Undeliverable",
"score": 0,
"syntax": { "is_valid": false, "has_typo": false },
"disposable": { "is_disposable": false },
"role": { "is_role_account": false },
"mx": { "has_mx": false, "records": [], "accepts_mail": false },
"smtp": null,
"provider": { "name": "Unknown", "type": "unknown" },
"cached": false,
"verified_at": "2026-02-17T12:34:56Z"
},
{
"email": "[email protected]",
"result": "Undeliverable",
"score": 10,
"syntax": { "is_valid": true, "has_typo": false },
"disposable": { "is_disposable": true },
"role": { "is_role_account": false },
"mx": { "has_mx": true, "records": ["mx.tempmail.org"], "accepts_mail": true },
"smtp": null,
"provider": { "name": "Unknown", "type": "unknown" },
"cached": false,
"verified_at": "2026-02-17T12:34:56Z"
},
{
"email": "[email protected]",
"result": "Deliverable",
"score": 85,
"syntax": { "is_valid": true, "has_typo": false },
"disposable": { "is_disposable": false },
"role": { "is_role_account": true },
"mx": { "has_mx": true, "records": ["mx.company.com"], "accepts_mail": true },
"smtp": { "can_connect": true, "is_deliverable": true, "is_catch_all": false, "reachability": "safe" },
"provider": { "name": "Unknown", "type": "unknown" },
"cached": false,
"verified_at": "2026-02-17T12:34:56Z"
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
total_count | int | Unique addresses in the request. Exact, case-sensitive duplicates are deduplicated before processing and validated once, so this can be lower than the number of emails you sent. |
processed_count | int | Emails that were processed |
successful_count | int | Validations that completed without error |
credits_consumed | int | Credits deducted (paid validations) |
free_validations | int | Free validations (disposable, typos, SMTP errors) |
results | array | One entry per unique address, in first-occurrence order. Match results to your input on the email field, not on position: duplicates in your request will not have a corresponding entry. |
Each item in results has the same structure as a single validation response.
Credit Consumption
Credits are checked before processing the batch. If you don't have enough credits for the entire batch, the request is rejected with 402.
Within the batch, free validations apply per the same rules as single validation:
- Disposable domains: free
- High-confidence typos (90%+): free
- SMTP connection errors: free
The response shows the exact breakdown: credits_consumed (billed) + free_validations (not billed) = processed_count.
Concurrency
Batch validations run up to 10 emails concurrently within a single request. This balances throughput against SMTP server rate limits. Results are returned in the same order as the input array regardless of processing order.
For lists larger than 100 emails, split them into multiple batch requests. The concurrency limit applies per request: you can run multiple batch requests in parallel from your server.
Validation History
All validations (single and batch) are recorded in your validation history:
GET https://api.mail.land/v1/validation/history| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | - | Opaque cursor from the previous page's nextCursor. Omit for the first page. |
limit | int | 20 | Items per page (max 100) |
result | string | - | Filter: Deliverable, Risky, Unknown, Undeliverable, Invalid |
from | datetime | - | Start date |
to | datetime | - | End date |
search | string | - | Search by email address |
{
"items": [
{
"email": "u***@example.com",
"result": "Deliverable",
"score": 95,
"is_disposable": false,
"is_role_account": false,
"has_valid_mx": true,
"smtp_deliverable": true,
"provider": "Unknown",
"was_cached": false,
"smtp_verified": true,
"credit_consumed": true,
"verified_at": "2026-02-17T12:34:56Z"
}
],
"nextCursor": "eyJ2IjoxLCJzIjoidmFsaWRhdGlvbjp2ZXJpZmllZGF0OmRlc2MiLCJrIjoi...",
"hasMore": true
}Email addresses in history are partially masked for privacy (e.g., u***@example.com).
Validation Statistics
Get aggregate statistics for your validation usage:
GET https://api.mail.land/v1/validation/stats| Parameter | Type | Default | Description |
|---|---|---|---|
from | datetime | - | Start date |
to | datetime | - | End date |
{
"total_validations": 450,
"deliverable": 320,
"risky": 65,
"undeliverable": 50,
"unknown": 15,
"deliverable_rate": 71.11,
"risky_rate": 14.44,
"undeliverable_rate": 11.11,
"unknown_rate": 3.33,
"credits_consumed": 380,
"free_validations": 70
}Limits
| Limit | Value |
|---|---|
| Max emails per batch | 100 |
| Concurrent validations per batch | 10 |
| SMTP timeout per email | 30 seconds |
| Max email length | 320 characters |
Error Responses
| Status | Code | Description |
|---|---|---|
400 | INVALID_REQUEST | At least one email is required |
400 | CONTENT_TOO_LARGE | Batch exceeds 100 emails |
402 | INSUFFICIENT_CREDITS | Not enough credits for the batch |
Best Practices
- Validate before importing: run batch validation on new subscriber lists before importing them to avoid bounce spikes.
- Filter by result: only import
Deliverableemails. TreatRiskyemails with caution and avoidUndeliverableentirely. - Check typo suggestions: when
syntax.has_typoistrue, present thesyntax.suggestionto the user for confirmation before sending. - Use during signup: validate emails in your registration flow with single validation to prevent bad addresses from entering your system.
- Monitor credit usage: check
/validation/creditsto track consumption and plan upgrades before hitting your quota limit.
Next Steps
- Single Email Validation: real-time validation for individual addresses
- Suppressions: manage suppression lists
- IP Warmup: warm up domains with clean, validated lists