Skip to Content
DocsAPI ReferenceEmail Validation

Email Validation

The Email Validation endpoint allows you to verify the deliverability and quality of email addresses. Use this endpoint to validate single or multiple emails in one request.

Endpoint

POST https://api.campaignkit.cc/v1/email/validate

Authentication

Include your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Request

Request Body

Send a JSON object with an array of email addresses to validate:

ParameterTypeRequiredDescription
emailsarrayYesArray of email addresses to validate (minimum 1 email)

Example Request

curl -X POST https://api.campaignkit.cc/v1/email/validate \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "emails": [ "valid@example.com", "invalid@fake-domain.xyz", "admin@company.com" ] }'

You can validate up to 100 email addresses in a single API request. For larger batches, consider breaking them into multiple requests or using batch validation features.

Response

Response Format

The API returns a JSON object with a results array containing validation data for each email, and the total credits used:

{ "creditsUsed": 3, "results": [ { "email": "valid@example.com", "contextId": "ctx_abc123", "result": { "syntax": "pass", "mx": "pass", "mailbox": "pass", "score": 10, "classifier": "valid", "description": [], "smtpResponse": "250 OK" } }, { "email": "invalid@fake-domain.xyz", "contextId": "ctx_def456", "result": { "syntax": "pass", "mx": "fail", "mailbox": "n/a", "score": 0, "classifier": "invalid", "description": ["domain"], "smtpResponse": "" } }, { "email": "john@gmai.com", "contextId": "ctx_typo01", "result": { "syntax": "pass", "mx": "fail", "mailbox": "n/a", "score": 0, "classifier": "invalid", "description": ["domain", "didYouMean"], "smtpResponse": "", "didYouMean": "john@gmail.com", "didYouMeanDomain": "gmail.com" } }, { "email": "admin@company.com", "contextId": "ctx_ghi789", "result": { "syntax": "pass", "mx": "pass", "mailbox": "n/a", "score": 4, "classifier": "risky", "description": ["verifyRejected", "role"], "smtpResponse": "" } } ] }

Response Fields

The top-level response contains:

FieldTypeDescription
creditsUsedintegerNumber of validation credits consumed by this request
resultsarrayArray of validation results for each email

Each item in the results array contains:

FieldTypeDescription
emailstringThe email address that was validated
contextIdstringUnique identifier for this validation result
resultobjectValidation result details

Validation Result Object

FieldTypeValuesDescription
syntaxstringpass, fail, n/aWhether the email syntax is valid
mxstringpass, fail, n/aWhether the domain has valid MX records
mailboxstringpass, fail, n/aWhether the mailbox exists (n/a if server doesn’t allow checking)
scoreinteger0-10Deliverability score (10 = best, 0 = will bounce)
classifierstringvalid, risky, invalidOverall classification of the email
descriptionarraystringsDetailed validation flags (see below)
smtpResponsestring-SMTP server response message (if available)
didYouMeanstring-Optional. Suggested corrected email when the domain looks like a typo of a well-known provider (e.g. john@gmail.com for an input of john@gmai.com). Omitted when no suggestion applies.
didYouMeanDomainstring-Optional. Suggested corrected domain only (e.g. gmail.com). Omitted when no suggestion applies.

Score Interpretation

Every address starts at 10. A failed syntax, domain or mailbox check ends validation with a score of 0, and a domain on the Spamhaus blocklist ends it with 1. Otherwise points are subtracted, and the score never drops below 0:

FindingPointsFlag
Mailbox couldn’t be verified−4verifyRejected
Domain is catch-all−1catchAll
Local part is blacklisted−8blacklist
Disposable address−8disposable
Role address (noreply@ −3, info@ −2, sales@ −1)−1 to −3role

Only one of blacklisted local part, disposable and role applies. In practice:

  • 10: All checks passed and the mailbox accepts mail
  • 9: Mailbox accepted, but the domain is catch-all
  • 7-9: Role address on an otherwise good domain
  • 6: Domain is fine, but the mailbox couldn’t be verified
  • 3-5: Role address whose mailbox couldn’t be verified
  • 1-2: Disposable address, blacklisted address or blocklisted domain
  • 0: Email will bounce (bad syntax, no mail server or unknown mailbox), or a disposable address that couldn’t be verified

Scores of 0-2 are undeliverable, disposable or blacklisted: exclude them from your sending lists. Review scores of 3-6 before sending, because the mailbox couldn’t be confirmed or the address is a role address.

Classifier Field

The classifier field provides a simple, categorical assessment of email quality:

  • valid: No risk found. Score 10, or 9 (catch-all domain)
  • risky: The mailbox couldn’t be verified (score 6), or a role, disposable or blacklisted address, or a blocklisted domain. Score 0-9, depending on the finding
  • invalid: Email is undeliverable and will bounce. Always score 0

Use classifier for quick filtering and routing decisions, and score for more nuanced quality assessment.

Description Flags

The description array contains detailed validation flags:

FlagMeaning
syntaxEmail format is invalid
domainDomain doesn’t exist or has no mail server (MX record)
mailboxThe mail server reported the mailbox as unknown
verifyRejectedMailbox couldn’t be verified (the server refused or didn’t answer verification)
catchAllDomain accepts all emails (catch-all server), so the mailbox check isn’t conclusive
roleRole-based email (admin@, info@, support@, etc.)
disposableDisposable/temporary email address
blacklistThe address is on a known blacklist, or the domain is on the Spamhaus blocklist
didYouMeanThe domain is unreachable but resembles a well-known provider; see the didYouMean and didYouMeanDomain fields for the suggested correction.

Suggested Correction (Did You Mean?)

When the submitted domain has no valid MX records and clearly resembles a well-known mail provider domain (e.g. gmai.com vs. gmail.com, yhaoo.com vs. yahoo.com), the API includes two optional fields on the result:

  • didYouMean — the suggested corrected email address (e.g. john@gmail.com)
  • didYouMeanDomain — the suggested corrected domain only (e.g. gmail.com)

Both fields are omitted from the response when no suggestion applies, so existing integrations that don’t read them are unaffected.

The matcher is intentionally conservative:

  • It only suggests when the submitted domain is unreachable. We never “correct” a domain that has a valid MX record.
  • It only matches against a curated list of well-known providers. Unrelated domains do not produce a suggestion.
  • It returns at most one candidate.

Treat the suggestion as advisory. Show it to the end user (e.g. as a “Did you mean <suggested>?” prompt at signup) rather than rewriting addresses silently in bulk. False positives are rare but possible, and the original input is what your users actually typed.

The same fields appear on entries returned by validation jobs and on the trailing cpkit.didYouMean and cpkit.didYouMeanDomain columns of CSV exports.

Code Examples

JavaScript (Node.js)

const fetch = require('node-fetch'); async function validateEmails(emails) { const response = await fetch('https://api.campaignkit.cc/v1/email/validate', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ emails }) }); const data = await response.json(); return data; } // Usage validateEmails(['test@example.com']) .then(data => { console.log(`Credits used: ${data.creditsUsed}`); data.results.forEach(item => { console.log(`${item.email}: ${item.result.classifier} (Score: ${item.result.score}/10)`); }); }) .catch(error => console.error('Error:', error));

Python

import requests def validate_emails(emails): url = 'https://api.campaignkit.cc/v1/email/validate' headers = { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' } data = {'emails': emails} response = requests.post(url, json=data, headers=headers) return response.json() # Usage data = validate_emails(['test@example.com']) print(f"Credits used: {data['creditsUsed']}") for item in data['results']: result = item['result'] print(f"{item['email']}: {result['classifier']} (Score: {result['score']}/10)")

PHP

<?php function validateEmails($emails) { $url = 'https://api.campaignkit.cc/v1/email/validate'; $data = json_encode(['emails' => $emails]); $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer YOUR_API_KEY', 'Content-Type: application/json' ]); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); } // Usage $data = validateEmails(['test@example.com']); echo "Credits used: {$data['creditsUsed']}\n"; foreach ($data['results'] as $item) { $result = $item['result']; echo "{$item['email']}: {$result['classifier']} (Score: {$result['score']}/10)\n"; } ?>

Error Responses

401 Unauthorized

Invalid or missing API key:

{ "error": { "code": "unauthorized", "message": "Invalid API key" } }

400 Bad Request

Invalid request format:

{ "error": { "code": "invalid_request", "message": "The 'emails' field is required and must be an array" } }

429 Too Many Requests

Rate limit exceeded:

{ "error": { "code": "rate_limit_exceeded", "message": "Rate limit exceeded. Please wait before making more requests" } }

Best Practices

Filter by Classifier

Use the classifier field for simple, reliable filtering:

const data = await validateEmails(emailList); const results = data.results; const valid = results.filter(r => r.result.classifier === 'valid'); const risky = results.filter(r => r.result.classifier === 'risky'); const invalid = results.filter(r => r.result.classifier === 'invalid'); console.log(`Valid: ${valid.length}, Risky: ${risky.length}, Invalid: ${invalid.length}`); console.log(`Total credits used: ${data.creditsUsed}`);

Filter by Score

For more granular control, use the score:

const results = data.results; const highQuality = results.filter(r => r.result.score >= 9); const acceptable = results.filter(r => r.result.score >= 6 && r.result.score < 9); const questionable = results.filter(r => r.result.score >= 3 && r.result.score < 6); const invalid = results.filter(r => r.result.score < 3); console.log(`High: ${highQuality.length}, Acceptable: ${acceptable.length}, Questionable: ${questionable.length}, Invalid: ${invalid.length}`);

Check Specific Flags

Look for specific issues in the description array:

results.forEach(item => { const { email, result } = item; if (result.description.includes('disposable')) { console.log(`${email} is a disposable email address`); } if (result.description.includes('role')) { console.log(`${email} is a role-based email (may have low engagement)`); } });

Handle Catch-All Domains

Catch-all domains accept all emails, so a passed mailbox check doesn’t prove the address exists.

Some domains only look catch-all because a security gateway in front of Microsoft 365 or Google Workspace accepts all mail. In that case CampaignKit also asks the Microsoft 365 or Google Workspace server behind the gateway. If that server confirms the mailbox, the address is verified (score 10) and has no catchAll flag. If it can’t confirm the mailbox, the result stays catch-all.

Check for the flag like this:

const hasCatchAll = result.description.includes('catchAll'); if (hasCatchAll) { // The server accepts any address, so this mailbox can't be confirmed console.log('Catch-all domain detected - mailbox verification unavailable'); }

Batch Processing

For large lists, process in batches of 100:

async function validateLargeList(emails) { const batchSize = 100; const allResults = []; let totalCreditsUsed = 0; for (let i = 0; i < emails.length; i += batchSize) { const batch = emails.slice(i, i + batchSize); const data = await validateEmails(batch); allResults.push(...data.results); totalCreditsUsed += data.creditsUsed; // Optional: Add delay to respect rate limits await new Promise(resolve => setTimeout(resolve, 1000)); } return { results: allResults, totalCreditsUsed }; } // Usage const { results, totalCreditsUsed } = await validateLargeList(largeEmailList); console.log(`Validated ${results.length} emails using ${totalCreditsUsed} credits`);

Validation Credits

Each email validation consumes one credit from your account. The API response includes a creditsUsed field showing how many credits were consumed by the request.

const data = await validateEmails(['email1@example.com', 'email2@example.com']); console.log(`Credits used: ${data.creditsUsed}`); // Output: Credits used: 2

You can also check your remaining credits in the CampaignKit dashboard .

Duplicate emails in the same request are deduplicated automatically and only consume one credit per unique email address. The creditsUsed field reflects the actual number of credits consumed after deduplication.

Need Help?

If you encounter any issues or have questions about email validation:

Last updated on