Validation

Groundmail validation checks an email address before you accept it or send to it. It works in two places:

  • On every send, automatically: recipients that cannot receive email are held back before they bounce. See Recipients held back by Groundmail validation.
  • On demand, with POST /validate: one address at a time, for signup forms and imports.

Both are free. Validation never sends anything to the address.

Base URL: https://api.groundmail.dev


Authentication

Authorization: Bearer gm_…

Any key with emails:send or emails:read (sending keys included). Call it from your server, never from the browser: the key must stay secret.


Validate an address

POST /validate

{ "email": "ada@gmial.com" }

200

{
  "email": "ada@gmial.com",
  "domain": "gmial.com",
  "verdict": "risky",
  "reasons": ["possible_typo"],
  "suggestion": "ada@gmail.com",
  "disposable": false,
  "role": false
}
Field Meaning
email The address as checked: trimmed and lowercased.
domain Its domain, or null when it has none.
verdict valid, risky or invalid (below).
reasons Everything that was found, in a fixed order. Empty for a clean valid.
suggestion The address with a likely-mistyped domain corrected (gmial.com → gmail.com, yahoo.con → yahoo.com), or null.
disposable A throwaway-inbox service.
role A team or queue mailbox (info@, support@, noreply@) rather than a person.

Verdicts and reasons

verdict Reasons that lead to it What to do
invalid syntax (the address cannot exist), no_mail_server (the domain does not exist or takes no email), mailbox_not_found (the mailbox is known not to exist), previously_bounced (it hard-bounced for a Groundmail sender in the last 90 days) Reject it, or ask for another address. Mail to it would bounce.
risky disposable, catch_all (the domain accepts every address, so the mailbox cannot be confirmed), possible_typo (see suggestion) Accept it, or ask "Did you mean …?" with suggestion.
valid none, or only role Accept it.

role is informational: role mailboxes receive email, so it never changes the verdict on its own. Whether to accept disposable and role addresses is your call; Groundmail never holds them back on send.

valid means nothing wrong was found, not that a person reads the mailbox. Validation does not open a connection to the recipient's mail server.

Errors

Status code When
400 validation_error email is missing or longer than 320 characters.
401 unauthorized Missing or invalid API key.
403 insufficient_scope The key has neither emails:send nor emails:read.
429 rate_limited More than 120 requests a minute or 5,000 an hour for this key. Wait for Retry-After.

A malformed address is not an error: it returns 200 with verdict: "invalid" and reasons: ["syntax"].


On a signup form

import { Groundmail } from 'groundmail'

const groundmail = new Groundmail(process.env.GROUNDMAIL_API_KEY!)

export async function checkSignupEmail(email: string) {
  const result = await groundmail.validate(email)
  if (result.verdict === 'invalid') {
    return { ok: false, message: 'That email address cannot receive email.' }
  }
  if (result.suggestion) {
    return { ok: true, message: `Did you mean ${result.suggestion}?` }
  }
  return { ok: true }
}

SDK

const result = await groundmail.validate('ada@gmial.com')
// result.verdict: 'valid' | 'risky' | 'invalid'

CLI

groundmail validate ada@gmial.com

Exits 2 when the verdict is invalid.

MCP

The validate_email tool takes email and returns the same result.

curl

curl -X POST https://api.groundmail.dev/validate \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@gmial.com"}'

On send

The send-time checks are on by default. An account owner can turn off the two that draw on what Groundmail has seen across senders (previously_bounced and mailbox_not_found) in Settings → Suppressions; invalid addresses and domains with no mail server are always held back. Held-back recipients are not added to your suppression list.


OpenAPI

openapi/groundmail.yaml (validateEmail)