← Blog
Jascha Kaykas-Wolff··7 min read

AI SDR Email Verification: Never Let the Agent Guess an Address

An AI SDR should never write an email address itself. Every address should come from a data source and pass a verifier such as ZeroBounce, NeverBounce or Kickbox inside the enroll tool, where valid results go out, invalid ones are suppressed permanently, and catch-all domains get a small daily cap of their own. If hard bounces on a campaign pass 2%, the agent stops sending until a person looks.

Models are good at patterns.

Ask one for the email address of the VP of Sales at a 40-person freight broker in Columbus and it will hand you one immediately, in the right format, with the company's real domain, and there is a fair chance that mailbox has never existed. Nothing in that answer looks wrong. An agent with a research tool on one side and a send tool on the other will fill the space between them with exactly this kind of plausible guess unless the tools make guessing impossible, and every guess that misses comes back as a hard bounce charged to your domain.

Our deliverability guide treats a 2% bounce rate as the point where list quality is the problem. This page is about how an agent gets past 2% without anyone noticing, and what the enroll tool has to refuse to keep it under.

Why bounces hurt more when an agent sends

A hard bounce is the receiving server saying the mailbox does not exist, usually with a 550 5.1.1 code. One of those means nothing. A steady stream of them reads, to Gmail and Microsoft, like a sender working from a bought or scraped list, which is the exact profile their filters were built to junk.

A human SDR with a stale list watches the bounce notices pile up in their inbox by Tuesday and stops. An agent usually never sees them. The bounce goes back to the sending mailbox, the agent works from a queue, and unless someone wired the notice back into the agent's state, it keeps enrolling from the same bad source all week.

What a verifier actually checks

It looks up the domain's MX records, opens an SMTP conversation with that mail server, asks whether it would accept mail for the address, and hangs up before sending anything. Fast. Cheap, too, well under a cent per check at volume.

Then it meets a catch-all domain. A catch-all server answers yes to every address at the domain, real or invented, and sorts things out after the message arrives, which means the verifier has learned nothing. A lot of B2B domains behave this way, and the share climbs among companies that put a security gateway in front of their mail. All three verifiers named here report it, under different labels, and the table below maps their labels to what we let the agent do.

Verification results and what the agent may do

ResultZeroBounce / NeverBounce / Kickbox labelWhat it meansWhat the enroll tool doesDaily cap
Validvalid / valid / deliverableThe mail server confirmed the mailbox existsEnrollThe normal mailbox cap (about 30 a day)
Invalidinvalid / invalid / undeliverableNo such mailbox, or the domain has no mail server at allRefuse, and add the address to the suppression table for good0
Catch-allcatch-all / catchall / risky (accept_all)The server says yes to every address at the domain, so the check proves nothingEnroll only if the address came from a data source. Drop it if code built it from a name pattern5 per mailbox per day, counted separately
Unknownunknown / unknown / unknownThe server timed out, greylisted the check, or refused to talkRe-check once after 24 hours, then treat a second unknown as catch-all0 until re-checked
Disposable, trap or abusespamtrap, abuse, do_not_mail / disposable / disposable flagA throwaway inbox, a known complainer, or an address that exists to catch list buyersRefuse and suppress. Never retry0

The catch-all row is where teams lose arguments with themselves. Dropping every catch-all address throws away real buyers at companies with strict mail setups, often the larger accounts. Sending to all of them unchecked is how a 1% bounce rate becomes 6% in a week. A separate, small cap lets some through while keeping the damage countable.

Never let the model write the address

This is the opinion we would defend in any room.

The enroll tool takes a contact ID from the data source, never a free-text email field. If the agent cannot find a sourced address for someone, the contact is skipped, and the agent says so in its run notes. We do allow pattern guessing (first.last@, f.last@ and so on), but only in a plain function we wrote, which tags the result source=pattern, runs it through the verifier, and throws it away on any result other than valid. A pattern guess on a catch-all domain is a coin flip, and we would rather lose the contact than flip coins with the sending domain.

This costs us contacts every week. Fine.

There is a second reason, and it comes from our prompt injection guide. If the model can type an address into a tool, then a reply or a research page can type one for it. Taking that field away closes the guessing problem and the hijacking problem with the same change.

Verify at enroll, and again before late follow-ups

A result from the day the list was built says little about a send five weeks later, because people change jobs and IT departments delete mailboxes on their own schedule. We re-verify any address whose last check is more than 30 days old, at the moment of enroll, and again before any follow-up step scheduled more than two weeks after the first touch. The check result and its timestamp go in the audit log next to the send, so when a bounce does happen you can see whether the address was verified yesterday or in August.

The bounce breaker

Verification runs before the send and has no idea what happens after. To catch that, the send tool keeps a rolling count of hard bounces over the last 100 sends per campaign, and when the count passes two it trips the kill switch, the same way the campaign ceiling does in our send limits guide. New sends pause. Replies keep flowing. Someone gets paged.

That only works if bounces actually reach the counter, which in practice means parsing delivery status notifications from the sending mailbox or subscribing to your sequencer's bounce webhook. Each hard-bounced address is written to the exclusion list the same minute, so no other campaign, and no second agent run, can try it again.

What verification cannot tell you

A valid mailbox can belong to someone who left in March and whose manager now reads everything sent to it.

Where each option fits (and where it does not)

We have no stake in which verifier you pick. We would choose by two things: how clearly it reports catch-all, and whether the API answers fast enough to sit inside a tool call without the agent timing out.

Where verification happensFitsFalls short
Data vendor “verified” flag (Apollo, ZoomInfo and similar)A free first pass on a list you already boughtIt reflects the vendor’s last check, which may be months old, and catch-all handling varies by vendor
Clay waterfall with a verifier stepTeams already enriching in Clay; the agent reads finished rows and never does lookups itselfThe verification is only as fresh as the last table run
Verification credits inside your sequencerSmall teams on Instantly or Smartlead who want one bill and one dashboardCheck how it labels catch-all before trusting it. Some lump catch-all in with valid
Dedicated verifier API (ZeroBounce, NeverBounce, Kickbox)An agent that enrolls contacts itself and needs a yes or no inside the tool callOne more vendor, one more API key, and results that still say “unknown” more often than you would like

If you already run Clay, keep verification there and let the agent read the output. Rebuilding a waterfall inside an agent is a month of work to arrive somewhere Clay already is. If the agent sources and enrolls contacts on its own, which is how most homegrown AI SDRs end up, put a dedicated verifier API behind the enroll tool and accept the extra vendor.

And if you would rather not build any of it yet, our service includes prospect sourcing with email verification, run by Mira. The first campaign is a free pilot, you see every address that was contacted and why, and after that pricing is per qualified meeting, agreed with you. A team that already has a clean Clay pipeline and an engineer who enjoys this work probably does not need us for it.

Trip it on purpose

Put five addresses that do not exist at a domain you own into a shadow mode list of 100, and confirm the enroll tool refuses all five and logs each refusal with the verifier's answer attached. Then switch on catch-all routing for a spare Google Workspace domain (it is a routing setting in the admin console), add twenty made-up addresses there, and check that five get through on day one and the rest wait. Last, feed the counter three fake bounces and watch for the page. If the phone stays quiet, the breaker is decoration.

Why we care

After the free pilot we get paid per qualified meeting, and a qualified meeting is one where the prospect shows up. Nobody shows up from a mailbox that was deleted in the spring. Mira runs 24/7 on OpenClaw from a Mac mini in San Francisco, sourcing and enrolling contacts without anyone watching each step, so the enroll tool is the only reviewer most of those addresses ever get. How Mira works walks through the rest, from ICP kickoff to booked call.

Jascha Kaykas-Wolff founded Mira Outbound and runs Mira, the outbound agent described across this site, on OpenClaw. These guides come from the guardrails that agent actually uses. kaykas.com