Verification is a pipeline, not one green badge

A verifier cannot turn an address into certainty with one lookup. Useful hygiene is layered because different checks answer different questions: is the string syntactically plausible, does the domain exist and publish mail routing, has your own system already seen a permanent failure, and is there any evidence the mailbox can receive mail? Keeping these layers separate prevents false confidence. A row that passes syntax is not “verified,” and a server that accepts a probe does not prove a named mailbox belongs to the intended person.

Start with deterministic checks that cost nothing to reputation

Normalize whitespace and casing, reject malformed addresses, and check that the domain can be resolved appropriately for mail. These steps catch obvious errors before any sender has to learn about them through an SMTP failure. On a 500-row list, finding twelve malformed or dead-domain rows in preprocessing is a straightforward win: those twelve mistakes never become receiver-facing bounces. Deterministic checks should happen on import and again before send if data can be edited downstream.

Apply your own suppression history as a hard gate

A third-party verifier does not know everything your mailboxes have learned. If `alex@example.com` previously returned a permanent 5.x.x failure, that address should remain suppressed across campaigns and sender mailboxes unless an operator deliberately clears the state with evidence. Store suppression centrally and consult it immediately before execution, not only during CSV import. This protects against an old list being re-imported later or the same contact arriving from a different enrichment source.

Treat mailbox-level checks as evidence with uncertainty

Mailbox verification can be limited by catch-all behavior, temporary server responses, greylisting, privacy controls, and providers that do not reveal recipient validity before accepting a message. Record the verifier's state rather than collapsing everything into valid/invalid. “Confirmed,” “catch-all,” “unknown,” and “temporary error” deserve different outbound treatment. An uncertain address may still be real, but a small sender should expose fewer uncertain rows at once and prefer contacts with stronger evidence.

Attach verification time and source to every result

Email data decays. A result from four months ago answers what the verifier observed four months ago, not what exists today. Store `verified_at`, verification method, list source, and any catch-all or role-account tags. When a batch produces hard bounces, those fields let you ask whether failures cluster in old data or one acquisition source. Without provenance, the only available response is to blame the domain or re-verify the entire database indiscriminately.

Verify again at the boundary where risk changes

Re-run cheap checks whenever a list is imported or materially transformed, and re-verify stale or uncertain rows before a campaign exposes current sender reputation. The exact freshness window depends on how fast the underlying market changes; job-change-heavy B2B lists may decay faster than stable institutional addresses. Use your own bounce history to tune the rule. If rows older than sixty days create materially more permanent failures, that is stronger local evidence than a universal blog recommendation.

Measure hygiene by prevented failures and explainable state

The goal is not a 100% “verification score.” It is to reduce preventable permanent failures and make remaining uncertainty explicit. Track how many rows each layer removes, how many uncertain rows are sent, and which verification states later hard-bounce. If catch-all rows produce a different failure profile than confirmed rows, adjust exposure. A useful hygiene system becomes more accurate from its own outcomes because every permanent failure feeds the suppression layer for future campaigns.

Keep a reason code instead of one final “verified” label

A useful hygiene pipeline preserves why a contact passed or failed each layer. Store fields such as syntax_valid, domain_resolves, prior_hard_bounce, catch_all, mailbox_check_result, verification_time and verification_source. This matters because the same final green badge can hide very different uncertainty. An address on a normal domain with no suppression history and a recent positive mailbox check is not equivalent to an address on a catch-all domain that merely accepted an SMTP probe. When a campaign later produces a hard bounce, the reason codes let you identify which verification states are underperforming and tighten only that segment. A single verified=true field destroys that feedback loop and encourages teams to treat probabilistic evidence as certainty.

Re-check state immediately before the message leaves the queue

Verification at import time is not enough when campaigns are scheduled days later. A recipient can reply from another sequence, unsubscribe, hard-bounce from a different mailbox, or be manually suppressed after the import. Add a final send-time gate that checks the global suppression store and the most recent contact state before submission. This is especially important when several mailboxes or domains share the same prospect pool. The verification pipeline answers “is this address technically plausible enough to consider?”; the send-time gate answers “is this contact still eligible right now?” Keeping those questions separate prevents a clean address from receiving mail after a business event has already told the system to stop.

Score verification by expected action, not just confidence

Different uncertainty states should produce different operating actions. A syntactically invalid address is rejected immediately. A domain that does not resolve is blocked until corrected. A prior hard bounce is suppressed globally. A recent normal mailbox verification may enter the standard tranche. A catch-all or inconclusive result can enter a smaller, separately measured tranche if the business data is otherwise strong. Encoding the action beside the reason makes the pipeline easier to audit than a generic confidence score. It also prevents downstream workers from interpreting “unknown” as “probably fine” simply because the row survived import.

Audit which layer actually prevented bad sends

Once a campaign finishes, count how many contacts were blocked by syntax checks, dead domains, prior suppression, role policy, catch-all uncertainty and mailbox-level verification. Then compare permanent failures among the contacts that were allowed through. This tells you where the hygiene system is earning its cost. If almost every prevented failure came from the global suppression list, invest in keeping that list synchronized. If one verification source repeatedly labels addresses clean that later return permanent recipient errors, reduce its weight. The goal is not the largest number of “verified” leads; it is an explainable pipeline that prevents avoidable failures before they reach the sender.

Field checklist

  • Normalize and syntax-check addresses before any mailbox-level verification.
  • Check domain/mail-routing viability separately from mailbox existence.
  • Apply central hard-bounce suppression immediately before every send.
  • Keep catch-all and unknown states distinct from confirmed-valid.
  • Store verification timestamp, method, and list source.
  • Feed actual permanent failures back into future verification and suppression decisions.

Primary sources

Standards and provider policies can change. These links are the reference points used for this field note.

  1. RFC 3463 — Enhanced Mail System Status CodesIETF / RFC Editor2.x.x, 4.x.x and 5.x.x delivery status code classes.
  2. Email sender guidelinesGoogle Gmail HelpAuthentication, TLS, DNS, spam-rate and bulk-sender requirements.
  3. Sender Requirements & RecommendationsYahoo Sender HubYahoo authentication, complaint-rate, DNS, unsubscribe and flow-control requirements and recommendations.
  4. SMTP Error CodesYahoo Sender HubYahoo guidance for temporary deferrals, permanent errors, unknown recipients and traffic-pattern problems.