# How to verify an email address without sending an email

Parse the address, resolve the domain's mail servers, then ask the server about the mailbox in an SMTP handshake that ends before any message is sent.

Date: 2026-09-03

You can verify an email address without sending anything by doing exactly what a sending mail server does up to the moment it would transmit the message: parse the address, look up the domain's mail servers in DNS, open an SMTP session, ask whether the recipient exists, and disconnect. The server's reply to that question is the verification. No message is transmitted, nothing lands in an inbox or a spam folder, and the recipient never knows.

## What happens in an SMTP handshake?

SMTP is a conversation. A sending server connects to one of the recipient domain's mail exchangers on port 25 and the two exchange a fixed sequence of commands before any content moves. The recipient check happens at the `RCPT TO` step, which is why a verifier can stop right after it.

| Step | Command | What the reply tells a verifier |
| --- | --- | --- |
| 1 | connect | Whether the host answers on port 25 at all |
| 2 | `EHLO` | Which extensions the server supports; nothing about the mailbox yet |
| 3 | `MAIL FROM` | Whether the server accepts the sender at all |
| 4 | `RCPT TO` | Whether the server accepts mail for this exact recipient: `250` yes, `550` no, `4xx` come back later |
| 5 | `QUIT` | The verifier leaves without ever sending `DATA` |

A real message only starts with `DATA`, the step a verifier never issues. Everything before it is a question, and questions do not generate bounces or deliveries.

## Why not just send a test email?

Sending a test message tells you less than a handshake and costs more. A test to a dead address produces a bounce, and bounces are the metric mailbox providers use to judge your reputation. A test to a spam trap gets your domain listed. A test to a real person is an unsolicited message. And the answer arrives asynchronously, minutes or hours later, in the form of a bounce notification you then have to parse.

The handshake answers in a second or two, synchronously, and leaves no trace on the receiving side beyond a log line.

## Which checks come before the handshake?

Most addresses are settled before any SMTP connection is opened, which is cheaper and often more decisive.

| Check | What it catches | Spaw result |
| --- | --- | --- |
| RFC 5322 syntax | Missing `@`, spaces, malformed domains | `invalid_syntax` |
| Provider username rules | Usernames Gmail, Outlook, Yahoo, iCloud, AOL or Proton would never issue | `invalid_local_part` |
| DNS: MX and address records | Domains with no mail server, a null MX, or MX targets that do not resolve | `no_mx_records`, `null_mx`, `mx_unresolvable` |
| Implicit MX | Domains with only an A record, which almost never accept mail | `implicit_mx` |
| Disposable list | Temporary inbox providers, including new burner domains routed through a listed operator's mail hosts | `disposable` |
| Role list | Shared inboxes such as `info@` or `billing@` | `role` |
| Typo-squat map | `gmail.con`, `hotmial.com` and similar, with a suggested correction | `likely_typo` |

Every one of those is a live DNS query or an open list lookup. The DNS part matters more than it looks: a domain whose MX records point at loopback or private addresses will never receive mail, and a domain publishing a null MX record has declared that it does not want any. Both are answered without a handshake, and Spaw answers them for free.

## What can the handshake not tell you?

Four things, and an honest verifier reports each of them instead of guessing.

A **catch-all server** answers `250` to every recipient, so the reply proves nothing about the specific mailbox. Corporate domains behind security gateways and many Microsoft 365 tenants work this way. Spaw reports `catch_all: true` with a risky verdict and adds `mailbox_confidence`, a recomputable estimate built from the domain's other signals.

**Greylisting** answers the first connection from an unknown sender with a temporary failure and expects a retry minutes later. A verifier cannot wait, so the mailbox stays unverified.

**Timeouts and refused connections** happen when a server slows sessions down on purpose, or when a firewall sits in front of it.

**Providers that hide the answer.** Some large providers accept every `RCPT TO` and reject later, or throttle verification traffic aggressively. Their addresses come back unverified more often than others.

In all four cases Spaw returns `smtp_checked: false`, `mailbox_exists: null` and an `smtp_reason` naming the cause, and the verdict rests on the checks above. You can ask for a second attempt: pass `callback_url` and the address is re-checked after 5 and 20 minutes, with the settled verdict pushed to you at no cost.

## How to do it with the Spaw API

One request, one credit for a deliverable or risky answer, nothing for an undeliverable one.

```bash
curl https://spaw.co/api/v1/email \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"email": "mia@acme.com"}'
```

```json
{
  "success": true,
  "data": {
    "email": "mia@acme.com",
    "deliverable": "deliverable",
    "reason": null,
    "risk_score": 0,
    "mx_found": true,
    "mx_provider": "google",
    "smtp_checked": true,
    "mailbox_exists": true,
    "catch_all": false,
    "smtp_reason": null,
    "// 16 more fields": "see the endpoint reference"
  },
  "meta": { "credits_used": 1, "credits_remaining": 9, "cache_hit": false }
}
```

The same lookup runs behind the free email checker, the dashboard, the batch endpoint for up to 50 addresses, and bulk jobs for up to 100,000. Six fixed addresses at `spaw.test`, such as `undeliverable@spaw.test`, answer canonical verdicts at no cost for integration tests.

## How to do it yourself

The handshake is simple enough to type by hand, which is the best way to understand it.

```text
$ nc aspmx.l.google.com 25
220 mx.google.com ESMTP
EHLO verifier.example.com
250-mx.google.com at your service
MAIL FROM:<check@verifier.example.com>
250 2.1.0 OK
RCPT TO:<nobody-here-12345@gmail.com>
550-5.1.1 The email account that you tried to reach does not exist.
QUIT
221 2.0.0 closing connection
```

Doing this at scale is where the difficulty starts. Most cloud providers block outbound port 25 by default. Receiving servers check the connecting IP's reverse DNS and reputation, and an IP that opens thousands of sessions without ever sending mail looks like an address harvester and gets throttled or blocked. Greylisting needs retry queues, catch-all detection needs per-domain memory, and every provider has its own quirks. That is the work a verification service does for you, and it is why Spaw runs the handshake through partner infrastructure with the guards described above rather than from the same machines that serve the API.

## What to do next

- Try an address in the [free email checker](/tools/email-checker) and read the `reason` and `smtp_reason` fields against the [verdict reference](/docs/reasons).
- Verify a list before a campaign with the [batch](/docs/api/verify-email-batch) or [bulk](/docs/api/create-bulk-job) endpoint; undeliverable answers are free.
- Report delivery outcomes back through the [feedback endpoint](/docs/api/report-delivery-feedback) so catch-all and unverified addresses at your domains get settled by real deliveries.
- Read [what a catch-all address is](/guides/catch-all-email-addresses) before deciding how to treat risky verdicts.

Reference: https://spaw.co/guides/verify-email-without-sending
