Sendry Docs
API Reference

Email Validation

Check the deliverability of an email address before sending — syntax, MX, disposable, role-account, and free-provider signals.

The Validation API returns a per-address deliverability assessment without sending mail. It performs syntax validation, MX lookup with a 3-second timeout, and signal checks for disposable domains, role accounts, free providers, and common-domain typos (e.g. gmial.comgmail.com).

Results for the same (org, email) pair are cached for 24 hours — repeat validations within that window return the cached result and do not consume quota. Transient DNS failures return result: "unknown" and are also not billed.


Validate a single email

POST /v1/validation/email

Requires sending_access scope.

Request body

FieldTypeRequiredDescription
emailstringYesEmail address to validate (1–254 chars).

Example request

curl -X POST https://api.sendry.online/v1/validation/email \
  -H "Authorization: Bearer $SENDRY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "stanley@example.com"}'

Response

{
  "email": "stanley@example.com",
  "result": "deliverable",
  "risk_score": 0,
  "checks": {
    "syntax": true,
    "mx": true,
    "disposable": false,
    "role_account": false,
    "free_provider": false,
    "did_you_mean": null
  }
}

Result values:

ValueMeaning
deliverableAddress looks valid (risk_score 0–30)
riskyValid syntax + MX but flagged (disposable / role account, risk_score 31–70)
undeliverableBad syntax or no MX records (risk_score 71–100)
unknownTransient DNS failure (SERVFAIL/timeout). Not billed, not persisted. Safe to retry.

Checks:

FieldTypeDescription
syntaxbooleanRFC-5322-lite validation of local + domain parts
mxbooleanDomain has at least one MX record (3s timeout per lookup)
disposablebooleanDomain matches a known disposable-email provider
role_accountbooleanLocal part is a role address (info@, admin@, support@, etc.)
free_providerbooleanDomain is a common free provider (gmail, yahoo, outlook, etc.)
did_you_meanstring | nullSuggested common domain if the input looks like a typo

Risk scoring:

SignalScoreResult
Bad syntax or no MX100undeliverable
Disposable domain70risky
Role account40risky
Free provider only20deliverable
All clear0deliverable

Example: disposable address

{
  "email": "test@mailinator.com",
  "result": "risky",
  "risk_score": 70,
  "checks": {
    "syntax": true,
    "mx": true,
    "disposable": true,
    "role_account": false,
    "free_provider": false,
    "did_you_mean": null
  }
}

Example: domain typo

{
  "email": "user@gmial.com",
  "result": "undeliverable",
  "risk_score": 100,
  "checks": {
    "syntax": true,
    "mx": false,
    "disposable": false,
    "role_account": false,
    "free_provider": false,
    "did_you_mean": "gmail.com"
  }
}

Validate a batch

POST /v1/validation/batch

Requires sending_access scope. Validates up to 100 emails per request with bounded DNS concurrency. For larger lists, send multiple batches.

Request body

FieldTypeRequiredDescription
emailsstring[]Yes1–100 email addresses. Each 1–254 chars.

Example request

curl -X POST https://api.sendry.online/v1/validation/batch \
  -H "Authorization: Bearer $SENDRY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      "stanley@example.com",
      "support@example.com",
      "user@mailinator.com"
    ]
  }'

Response

{
  "results": [
    {
      "email": "stanley@example.com",
      "result": "deliverable",
      "risk_score": 0,
      "checks": { "syntax": true, "mx": true, "disposable": false, "role_account": false, "free_provider": false, "did_you_mean": null }
    },
    {
      "email": "support@example.com",
      "result": "risky",
      "risk_score": 40,
      "checks": { "syntax": true, "mx": true, "disposable": false, "role_account": true, "free_provider": false, "did_you_mean": null }
    },
    {
      "email": "user@mailinator.com",
      "result": "risky",
      "risk_score": 70,
      "checks": { "syntax": true, "mx": true, "disposable": true, "role_account": false, "free_provider": false, "did_you_mean": null }
    }
  ]
}

If the batch exceeds quota mid-way, the request aborts with 429 validation_quota_reached. Already-counted validations stay persisted.


List past validations

GET /v1/validation

Requires read_only scope. Cursor-paginated by validation id, newest first.

Query parameters

ParameterTypeDefaultDescription
limitnumber50Results per page (1–100).
cursorstringValidation id from a previous response's next_cursor.
resultstringFilter by result: deliverable, undeliverable, risky, unknown.
email_domainstringFilter by email domain (lowercased).

Response

{
  "data": [
    {
      "id": "evl_abc123",
      "email": "stanley@example.com",
      "email_domain": "example.com",
      "result": "deliverable",
      "risk_score": 0,
      "checks": {
        "syntax": true,
        "mx": true,
        "disposable": false,
        "role_account": false,
        "free_provider": false,
        "did_you_mean": null
      },
      "created_at": "2026-05-30T14:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Aggregate stats

GET /v1/validation/stats

Requires read_only scope. Returns counts per result type for the given window. Defaults to the last 30 days.

Query parameters

ParameterTypeDefaultDescription
fromstringnow − 30 daysISO 8601 lower bound (inclusive).
tostringnowISO 8601 upper bound (inclusive).

Response

{
  "from": "2026-04-30T14:00:00Z",
  "to": "2026-05-30T14:00:00Z",
  "total": 1843,
  "counts": {
    "deliverable": 1502,
    "undeliverable": 211,
    "risky": 117,
    "unknown": 13
  }
}

Every status is always present in counts, even when zero, so dashboards can render empty buckets without extra logic.


Quotas

Monthly validation quota is enforced per plan, atomically at insert time. unknown results from transient DNS failures are not counted.

PlanMonthly validations
Free100
Pro5,000
Business25,000
EnterpriseUnlimited

When the quota is reached, the API returns 429 validation_quota_reached. Cached results (same email within 24h) bypass the quota.


Errors

StatusCodeWhen
401unauthorizedMissing or invalid API key
403forbiddenAPI key lacks the required scope
422validation_errorMissing/empty email, empty batch, or batch over 100
429validation_quota_reachedMonthly plan quota exhausted
429rate_limitedRequest rate limit exceeded

On this page