Blog / October 28, 2026

How to Verify an Email Address: Five Checks Before You Send

Learn how to verify an email address in five steps: syntax, DNS and MX checks, SMTP probing, risk verdicts, and API pipelines with code and edge cases.

How to Verify an Email Address: Five Checks Before You Send
On this page

Sending to 100,000 records with a 5% invalid rate creates about 5,000 hard bounces before a single person reads the message. Unverified lists commonly carry 10% to 15% undeliverable addresses from typos, expired accounts, and closed domains, and small unchecked batches can sit at 15% to 20% invalid or risky. MailTester's notes on how many test emails to check is where those ranges come from, and the same guidance says layered checks can cut hard bounces by as much as 90% in tested workflows. Results still depend on the list and the provider.

Learning how to verify an email address means treating the outcome as a risk class, not a yes or no. Syntax can be perfect on a dead domain. A server can accept a recipient and still filter, quarantine, or drop the message later. I would not ship a pipeline that collapses every address to valid or invalid.

Table of Contents

  • Five Steps to Verify an Email Address in Order
    • Shape the address before you touch the network
    • Ask DNS, then ask the mail host
  • What Each Verification Result Means and What to Do
    • The catch-all test
  • Why a Passing Check Can Still Be Wrong
  • Building the Checks in Python
    • The functions, in call order
    • What to write down
  • Choosing Between an In-House Script and an API
    • Keep the bill attached to real work
  • Rules for Keeping a Verified List Clean
  • Start With a 200-Address Test Run

Five Steps to Verify an Email Address in Order

A single regex, or one SMTP poke, will lie to you. Run a staged pipeline, cheapest checks first, and stop as soon as a step proves the address cannot receive mail.

A five-step flowchart illustrating the process of verifying email addresses to improve list quality and sender reputation.

Shape the address before you touch the network

  1. Normalize the input. Trim surrounding whitespace. Keep the local-part's case unless that provider defines case-insensitivity. Apply IDNA to internationalized domains so the DNS query uses the ASCII form. Aggressive lowercasing of the local-part creates false mismatches on systems that still treat case as significant.
  2. Check syntax against the Internet Message Format. RFC 5321 sits on top of those address rules in RFC 5322. Require one unquoted @, legal local-part characters, valid domain labels, and no consecutive dots. A quoted local-part is rare in business lists. If your parser cannot handle it, reject it rather than guessing.

Ask DNS, then ask the mail host

  1. Query DNS for MX records. A domain receives mail through its MX records, or through its A or AAAA records when no MX exists. A null MX means the domain accepts no mail. Cache the answer for the record's TTL. An empty or null result is a permanent failure, so do not open a socket.
  2. Open an SMTP session with the preferred MX host. Send EHLO or HELO, then MAIL FROM, then RCPT TO. Map the reply class and stop. A 2xx response is acceptance for that transaction only. It is not proof a person owns or reads the mailbox. The protocol says a server must not return a successful VRFY or EXPN unless it has verified the address, but many hosts disable those commands or answer vaguely to limit abuse. Use a connection timeout and a small retry cap. Never send a message during verification.
  3. Classify and store the reason. Separate accepted, rejected, temporary failure, and unknown. Log the stage that decided it. Greylisting, catch-all routing, disabled accounts, and later policy filters can all sit behind an accept.
Practical rule: Steps 1 to 3 are cheap and should run on every record. Step 4 is the expensive one. Cache it, rate-limit it, and do not repeat it on a fresh result.

What Each Verification Result Means and What to Do

The useful output is a risk category plus the next action. A binary flag throws away the cases where you should quarantine, slow down, or suppress.

ResultWhat triggers itWhat to do
Valid (accepted)SMTP 250 for the recipient, with a clean MX hostSend, and keep monitoring bounces
InvalidSyntax failure, no usable MX, or SMTP 550 rejectionSuppress; never send
Risky or catch-allRandom mailbox on the same domain is also acceptedRate-limit and send with care
DisposableDomain belongs to a temporary-mail serviceSuppress
Role-basedLocal part such as info, support, or salesSend only with a clear reason; low person relevance
Unknown or temporarySMTP 451, timeouts, greylisting, throttlingQuarantine and retry later
Null MXDomain publishes a null MX and accepts no mailSuppress

The catch-all test

After syntax and DNS pass, probe a randomized mailbox on the same domain. If the server accepts both the target and the random address, you cannot establish that the mailbox exists. That is a catch-all email domain. Suppress it or rate-limit automated outreach. Do not label it valid. Role accounts such as info@ or support@ may be deliverable and still have low person-level relevance. Disposable domains can expire quickly, so suppress them.

Judge the pipeline by downstream bounces, not by the verifier's own labels. Published benchmarks put hard bounces on a well-maintained list around 0.21% and soft bounces around 0.70%, while a separate large cold-email dataset reports about 2.2% blended bounces, per Email List Checker's deliverability statistics. Treat those as monitoring bands. Consent, list age, reputation, provider mix, and targeting all move the number. Quarantine unknown and temporary results, recheck stale records before sending, stop when hard bounces approach 2%, and investigate urgently above 5%. Those thresholds are industry guidance, not protocol rules.

Log normalization, syntax, DNS result, SMTP response class, catch-all result, timestamp, and retry reason. You can audit false positives without keeping a mailbox-enumeration dump.

Why a Passing Check Can Still Be Wrong

A clean syntax check plus a live MX record proves a domain accepts mail. It does not prove the mailbox. BounceZero's deliverability benchmarks found that only 0.3% of invalid addresses failed at the DNS level, meaning most dead addresses sit on domains with working mail infrastructure, and reported 12.3% invalid addresses across more than 10 million checks. Because that study comes from a verification vendor, treat the rate as directional.

Read a verifier's output as a risk grade, not a verdict. Syntax validation, DNS inspection, SMTP probing, catch-all detection, role detection, disposable screening, and real deliverability are different questions. Each passing check narrows the range of ways the address can fail; none of them closes it.

An SMTP accept is evidence about one transaction. On a catch-all domain the server may accept nearly any recipient, so a positive reply cannot prove that inbox exists. Greylisting often returns a temporary failure on first contact and accepts later. Many servers disable VRFY or answer ambiguously so outsiders cannot enumerate users. Acceptance also says nothing about a later spam filter, a full mailbox, or a disabled account that the gateway still acknowledges.

A "valid" label says nothing about whether the person will read or welcome the message. Mailbox status, provider filtering, sender reputation, authentication, consent, and engagement are separate dimensions. If you need the sending side of that picture, test placement with a deliverability test rather than trusting the verifier's label.

Results go stale too. A verdict describes the address on the day you checked it, and people leave jobs, abandon inboxes, and lose domains. Treat verification as perishable and re-run it before any send built on old results.

Downgrade your confidence even when SMTP returns 250 if any of these apply.

  • Catch-all result: the random probe was accepted, so the specific mailbox is unproven.
  • New or thin domain: mail infrastructure exists, but the address has little history.
  • Role account: info, support, or sales may reach a shared queue rather than a person.
  • Prior soft bounces: earlier temporary failures mean this address belongs in quarantine.

Building the Checks in Python

A small pipeline is enough to see what each check can prove. Five functions, called in order, keep the failure mode next to the step that owns it. The screen below is the shape of that pipeline, not a finished product.

A laptop screen displaying a Python code tutorial for an email validation pipeline process on a desk.

The functions, in call order

normalize() strips surrounding whitespace and lowercases only the domain. Leave the local-part alone. Lowercasing the whole string will "fix" addresses that were never broken and hide real mismatches.

syntax_ok() applies a conservative pattern. Reject a missing @, a repeated @, and consecutive dots. Do not try to accept every legal quoted local-part. Business lists almost never need them, and a loose pattern will pass garbage into DNS.

mx_hosts() uses dnspython, sorts MX hosts by preference, and falls back to an A record when no MX exists. A null MX returns an empty list, which you store as a permanent "accepts no mail" result. Cache answers by TTL so a 50,000-row file does not hammer the same zone.

smtp_probe() connects to the top MX host on port 25 with a 10-second timeout. It sends EHLO, MAIL FROM from a domain you control, and RCPT TO for the target. Map 250 to accepted, 550 to rejected, and 4xx to temporary. Cap retries at two. Many cloud hosts block outbound port 25, so this step fails closed on a laptop or a default VPC. That block is why teams move the SMTP stage to a network that allows it, or to a provider.

catch_all_probe() sends RCPT TO for a random local-part on the same domain. If both accept, mark risky. Do not upgrade the target to valid.

This walk-through shows the same session shape, including where a timeout should stop the probe.

What to write down

For each address, store the normalized form, syntax pass or fail, DNS outcome, SMTP class, catch-all flag, timestamp, and retry reason. Those fields are what you need when a provider starts answering 450 to everything and your "valid" rate suddenly jumps.

Choosing Between an In-House Script and an API

The script above proves the logic. It is a poor production sender if port 25 is blocked, if you cannot absorb greylisting, or if nobody owns the retries. An API trades that control for speed.

CriterionIn-house SMTP scriptVerification API
Setup effortDays to weeks for MX, timeouts, and retriesMinutes to add a key and call an endpoint
Port 25 accessMust be open from your host; often blocked on cloud IPsHandled by the provider
Catch-all and greylistingYou write and tune the logicUsually returned as a risk category
MaintenanceOngoing, as providers change responsesMaintained by the vendor
Cost visibilityInfrastructure and engineering timePer-verification credits; check each vendor's billing rules

Keep the bill attached to real work

Deduplicate before you call anything. Cache a result per address for as long as you trust it. Send large jobs through a bulk endpoint with webhooks instead of one request per row, and log which provider answered. Billing rules differ. Some vendors charge for every returned verdict, including invalid ones. Others charge nothing when no provider returns a result.

I would start with the script only when I control the network and the volume is small. For a list you will mail, our email verifier runs through a waterfall of licensed providers and is marked verified only when that waterfall returns a positive deliverability signal. An invalid verdict is a result and bills 2 credits. A call bills zero if no provider returns a result. Read that distinction in any vendor's docs before you estimate a cleanup, because a dirty list full of confirmed invalids is not a free run.

Rules for Keeping a Verified List Clean

A one-time cleanup decays. The routine below is what keeps the next campaign inside a healthy bounce band.

A graphic showing three essential rules for keeping an email marketing contact list verified and clean.
  1. Recheck stale records before the next send. Addresses age out through job changes and closed domains. Treat anything old enough to be doubtful as unverified again, and read that pattern as data decay, not as a one-off typo.
  2. Suppress every hard bounce and every invalid verdict immediately. Do not "try once more" on a 550 or a syntax failure.
  3. Quarantine unknown and temporary results. Do not send them, and do not delete them. Retry with a limit, then decide.
  4. Track hard-bounce rate per campaign. Pause and review when it leaves the warning band you set, and treat a sharp jump as urgent. Those bands are cited industry guidance, not rules in the mail protocol.
  5. Confirm SPF, DKIM, and DMARC. DMARC Guard's summary of Google and Yahoo requirements notes that both require those three for bulk senders, and that Gmail's bulk threshold is 5,000 messages per day to personal Gmail accounts. A verifier cannot place mail in the inbox. Authentication and sending behavior still have to hold.

Probing a mailbox can process personal data, create logs, and trip provider defenses. Keep only the results you need, document the processor and the retention period, and do not send test messages without consent. Give people a suppression path if they do not want their address tested. Preserve an unknown state, retry with rate limits, and pair verification with permission checks. Otherwise "deliverable" is just an optimistic label.

Start With a 200-Address Test Run

A pipeline that mislabels twenty addresses will mislabel twenty thousand. Prove it on a small set before you point it at the full file.

  1. Pick about 200 addresses you can already judge. Mix known-good mailboxes, known-bad ones, a few catch-all domains, disposable domains, and role accounts.
  2. Run them through the pipeline and compare each verdict with what you already know. A wrong answer on a clean mailbox points at the SMTP or deliverability stage. A wrong answer on a dead address points at syntax or MX handling. Fix the step that mislabeled it before you trust the rest.
  3. Then run the full list in batches and watch the bounce rate on the first send. If the small set was wrong, the big set will be wrong faster.

For an API route, sign up at app.richapi.ai, copy your API key, and run that same 200-address set before you queue the file. Each email_verifier call costs 2 credits, and a lookup that returns no result bills zero, so the sample costs little even if part of it comes back empty.

RichAPI puts an email verifier with deliverability checks behind a single API key, routing each lookup through a waterfall of licensed providers. A result is marked verified only when the waterfall returns a positive deliverability or reachable signal. Visit RichAPI to run a small test batch and compare the verdicts against addresses you already know.

More from the blog