How to verify emails at signup without hurting conversion

Verify at the form, but make the check invisible and forgiving: suggest a fix for obvious typos inline, block only addresses you can prove are dead, treat risky and unknown gently, and fail open the instant the API is slow or errors — so the Create Account button is never held hostage to a verification call. Done right, verification removes fake and mistyped signups while your conversion rate stays flat.

Written & reviewed by the VerifyAnyEmail team · Last reviewed July 2026

The direct answer: catch typos inline, block only the definitely-invalid, and fail open on everything else. A signup form has one job — get real people through it — so verification has to earn its place by removing bad data without adding friction for good users. That means an async check that can't stall the button, a "did you mean" hint for common misspellings, blocking reserved for a confirmed undeliverable, and a hard rule that a timeout or server error always lets the user through. Here is how to build exactly that.

The one principle: fail open

Every design choice below flows from a single rule — when verification can't give you a confident "no", let the signup proceed. Verification is a live conversation with a remote mail server, and remote servers greylist, tarpit, rate-limit and time out. If your form treats "I couldn't reach the server in time" the same as "this address is fake", you will reject real customers over transient network conditions — the worst possible trade, because you never even learn you lost them. So the only status that blocks is the one the verifier is certain about: undeliverable. risky, unknown, and any error or timeout all pass. You accept a few bad addresses to avoid rejecting good people, and you clean up the rest later with list cleaning.

Layer 1 — inline typo suggestions (no API, no friction)

The single highest-ROI thing you can add costs you nothing per check: an inline "did you mean gmail.com?" hint. A large share of bad signup addresses aren't fake at all — they're real people who typed gmial.com, gmail.con or hotmial.com and would happily fix it if you pointed it out. Detect a domain that's one edit away from a common provider, show a one-tap correction, and you recover those users before a single API call happens.

<!-- The email field. The verify call is async, so the button is never blocked. -->
<form id="signup">
  <label for="email">Work email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />
  <p id="email-hint" class="hint" hidden></p>
  <button type="submit">Create account</button>
</form>
// A "did you mean" typo hint that runs on blur — pure client-side, no API,
// no key. It never blocks submit; it only suggests. This alone recovers a
// large share of mistyped addresses ("gmial.com" -> "gmail.com").
const COMMON = ['gmail.com', 'outlook.com', 'hotmail.com', 'yahoo.com', 'icloud.com'];
const input = document.getElementById('email');
const hint = document.getElementById('email-hint');

input.addEventListener('blur', () => {
  const value = input.value.trim().toLowerCase();
  const domain = value.split('@')[1];
  if (!domain) return;
  // If the domain is one edit away from a common provider, offer the fix.
  const match = COMMON.find((d) => editDistance(domain, d) === 1);
  if (match && match !== domain) {
    const fixed = value.replace(domain, match);
    hint.hidden = false;
    hint.innerHTML = `Did you mean <button type="button" id="fix">${fixed}</button>?`;
    document.getElementById('fix').onclick = () => {
      input.value = fixed; hint.hidden = true;
    };
  }
});

This is a suggestion, never a wall: it offers a fix and gets out of the way. For a more thorough, provider-aware version of the same idea, point users at the Email Typo Checker, and see the fuller pattern in the results-explained guide.

Layer 2 — verify on the server, asynchronously

The real mailbox check belongs on your server, never in the browser — a verification API key shipped to the client is a key anyone can steal and spend (more in protect your API keys). Two UX rules make the server call painless. First, keep the timeout short — three to five seconds, not ten — because a signup form is interactive and a user won't wait. Second, never let the check block the button's responsiveness: either await it with that short timeout, or accept the signup immediately and verify in the background, downgrading the account only if it comes back undeliverable. Both are fine; awaiting with a tight timeout is simplest and gives instant feedback.

Do the cheap local syntax gate first so you never spend a probe on obvious junk, then call the verifier and branch:

// The server route. Verification runs, but it can only BLOCK on a definite
// negative. Everything else — risky, unknown, or any error — is allowed
// through (fail-open) so a slow API or an edge case never costs you a signup.
app.post('/signup', async (req, res) => {
  const email = String(req.body.email ?? '').trim();

  // 1. Cheap local gate: no probe for obvious garbage.
  if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
    return res.status(400).json({ error: 'Enter a valid email address.' });
  }

  // 2. Verify with a short timeout. verifyEmail() NEVER throws — on any
  //    failure it returns { status: 'unknown' }.
  const result = await verifyEmail(email, { timeoutMs: 4000 });

  // 3. Block ONLY on a confirmed dead address. Offer the typo fix if we have one.
  if (result.status === 'undeliverable') {
    return res.status(422).json({
      error: 'That address does not exist — check for a typo.',
      suggestion: result.suggestion ?? null,
    });
  }

  // 4. risky / unknown / deliverable all proceed. Tag risky for later review.
  const emailRisk = result.status === 'risky' ? result.subStatus : null;

  // ... create the user, store emailRisk ...
  return res.status(201).json({ ok: true });
});

Notice what does not block. A risky result (a catch-all domain, a role address like support@, or a possible disposable) is accepted and merely tagged — because plenty of real customers sit behind catch-all business domains, and blocking them at the form is the classic way to quietly bleed signups. unknown is accepted for the same reason it exists: the server couldn't answer, which is not the user's fault.

How each status should behave at the form

  • deliverable → accept silently. No message, no delay. The happy path must feel like there was no check at all.
  • undeliverable → block and help. This is the only hard stop. Show a friendly, specific message ("that address doesn't exist — check for a typo") and surface result.suggestion as a one-tap fix if the API returned one. Never show a scary error; frame it as a typo, because usually it is.
  • risky → accept, tag, decide later. Let them in, store the sub-status, and handle it downstream — segment role addresses, watch disposables. The form is the wrong place to adjudicate a catch-all domain.
  • unknown / timeout / error → accept, always. This is fail-open in action. Re-verify these addresses later in a batch rather than punishing the user for a slow SMTP server.

Common ways signup verification backfires

  • Blocking on unknown or risky. The fastest way to tank conversion. These are not confirmed-bad — treat them as pass-with-a-note.
  • A long or missing timeout. Ten seconds on a submit button feels broken. Cap it low and fail open when it fires.
  • Verifying in the browser. It leaks your key and adds a round trip the user can see. Always server-side.
  • Turning typo help into a hard error. A suggestion recovers the user; a rejection loses them. Suggest, don't block, for anything that might be a slip.
  • Rejecting plus-addressing or long local parts. jane+news@gmail.com is a perfectly valid, deliverable address. Don't let an over-strict regex do the verifier's job badly.

Test it without spending credits

Every branch of this logic can be tested deterministically. Any address at @test.verifyany.email returns a fixed status and charges nothing — point your integration tests at deliverable@, undeliverable@, risky@ and unknown@test.verifyany.email to confirm that only undeliverable blocks and the other three pass. For manual spot-checks, drop any address into the Email Verifier. When you're ready to wire the API into a specific stack, the Node.js & Express guide has the reusable client this page's server example assumes.

Get the balance right and verification becomes invisible: real users sail through, typos get a gentle nudge, fakes get stopped, and your conversion rate never notices it's there.

Check your domain in seconds

Run the free diagnostics referenced in this guide — no sign-up needed.