Catching Email Typos at Signup: gmial.com, hotmial.com and "Did You Mean" UX

Some of the worst signups aren't fake at all. They're real people who typed jane@gmial.com, clicked "Create account", and never heard from you again. Your welcome email bounced, the verification link never arrived, and a week later they either gave up or opened a support ticket titled "can't log in".

Typo'd addresses are one of the few email problems where the fix is almost entirely a UX problem. You don't need to block anyone. You need to notice the likely mistake and ask the user, while they're still on the page.

Why a typo costs more than one signup

A mistyped address is a quiet failure. Nothing errors, the account gets created, and every downstream step breaks:

  • The user is lost. They can't confirm their account, reset their password or receive receipts. Many won't come back to try again.
  • You get a bounce. Every message you send to gmial.com or yahooo.com either bounces or goes nowhere. Hard bounces count against your sender reputation, and a steady trickle of them from signups adds up.
  • Support gets the ticket. "I never got the email" and "can't log in" tickets are slow to resolve, because the first step is figuring out the address is wrong.
  • Your data is dirty. The account record now holds an address that will never work, and it sits in your list until someone cleans it.

A regex doesn't help here. jane@gmial.com is perfectly valid syntax. The problem is that the domain is almost certainly not what the user meant.

What a typo looks like

Most typos land on the domain, and on a handful of very popular providers. A few patterns I see over and over:

Typed Probably meant Kind of typo
gmial.com gmail.com Swapped letters
gmai.com gmail.com Missing letter
hotmial.com hotmail.com Swapped letters
yahooo.com yahoo.com Extra letter
outlok.com outlook.com Missing letter
gmail.con gmail.com Wrong TLD

Some of these domains exist and some don't. Either way, a person who types gmial.com almost never has a mailbox there. That's why the right check is "does this look like a near-miss of a well-known provider?", not just "does this domain exist?".

Detecting typos with fast mode

MailRambo has a free fast mode built for signup forms. It never contacts the mailbox, so it's quick (well under a second) and free on every plan. It checks syntax, domain typos, MX records, disposable providers and role addresses.

Call it with ?mode=fast:

curl -X POST "https://www.mailrambo.com/v1/verify?mode=fast" \
  -H "Authorization: Bearer mr_live_..." \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@gmial.com"}'

For a likely typo, you get reason: "possible_typo" and a top-level suggestion with the corrected address:

{
  "email": "jane@gmial.com",
  "mode": "fast",
  "deliverable": false,
  "reason": "possible_typo",
  "suggestion": "jane@gmail.com",
  "checks": { "mx": true, "disposable": true, "role_account": false, "free_email": true },
  "credits_remaining": null
}

Note that suggestion is the full address, not just the domain, so you can show it as-is. Fast mode returns deliverable: false when it finds a problem (invalid_syntax, possible_typo, disposable, no_mx) and null with reason: "fast_check_passed" otherwise. It never returns true, because it doesn't check the mailbox.

The "did you mean" UX

This is the part that matters most. A few rules I'd follow:

Suggest, don't block

A possible_typo result is a guess. Some people really do use unusual domains. So show the suggestion as a question, and let the user continue with what they typed if they insist:

Did you mean jane@gmail.com? [Use this] ยท [Keep jane@gmial.com]

Never auto-correct silently

It's tempting to just replace gmial.com with gmail.com on the server. Don't. If the guess is wrong, you've created an account for an address the user never typed, and they have no idea why their emails go to someone else. Any change to the address should be a click the user makes.

Check on blur, not on every keystroke

Run the check when the field loses focus (or when the user submits). Checking on every keystroke flashes errors at people halfway through typing. And since it's a network call, debounce it.

Keep the key on the server

The browser should call your own endpoint, which calls MailRambo. An API key in client-side code is visible to anyone.

Fail open

If the check times out or errors, let the user through. A typo check is a convenience; it should never be the reason a real signup fails.

Code: a small server endpoint

Here's a Next.js route handler that returns only what the form needs:

// app/api/check-email/route.ts
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const { email } = await req.json();
  try {
    const res = await fetch("https://www.mailrambo.com/v1/verify?mode=fast", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.MAILRAMBO_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email }),
      signal: AbortSignal.timeout(3000),
    });
    if (!res.ok) return NextResponse.json({ ok: true, skipped: true });
    const { deliverable, reason, suggestion } = await res.json();
    return NextResponse.json({ ok: deliverable !== false, reason, suggestion: suggestion ?? null });
  } catch {
    return NextResponse.json({ ok: true, skipped: true }); // fail open
  }
}

And the client side, checked on blur:

const input = document.querySelector("#email");
const hint = document.querySelector("#email-hint");

input.addEventListener("blur", async () => {
  hint.textContent = "";
  const value = input.value.trim();
  if (!value) return;

  const res = await fetch("/api/check-email", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email: value }),
  }).then((r) => r.json());

  if (res.reason === "possible_typo" && res.suggestion) {
    hint.innerHTML = `Did you mean <button type="button" id="use-suggestion"></button>?`;
    const btn = hint.querySelector("#use-suggestion");
    btn.textContent = res.suggestion; // textContent, not HTML: never inject user input
    btn.addEventListener("click", () => {
      input.value = res.suggestion;
      hint.textContent = "";
    });
  } else if (res.reason === "disposable") {
    hint.textContent = "Please use a permanent email address.";
  } else if (res.reason === "no_mx" || res.reason === "invalid_syntax") {
    hint.textContent = "That address can't receive email. Check for typos?";
  }
});

For a typo, the form suggests; it doesn't refuse. For disposable, no_mx and invalid_syntax, you have firmer grounds to block on submit, because those can't be a real inbox the user owns (or, for disposable, one you'll be able to reach later).

Messages for each fast-mode reason

reason deliverable Suggested message Block submit?
possible_typo false "Did you mean {suggestion}?" No, ask
invalid_syntax false "Please enter a valid email address." Yes
no_mx false "That domain can't receive email. Check for typos?" Yes
disposable false "Please use a permanent email address." Your call
fast_check_passed null (nothing) No

role_account doesn't appear as a fast-mode failure; it shows up as a flag in checks.role_account, which you can ignore for most signup forms.

After signup: the full check

Fast mode tells you nothing is obviously wrong. It doesn't confirm the mailbox exists. If you want that, run a full check (1 credit) after the account is created, in a background job, before your first marketing send. Repeat checks of the same address within 24 hours are free, so re-checking on a retry costs nothing.

For the wider picture of form validation (disposable blocking, timing, what to do when the API is down), see real-time email validation on signup forms. If you want to try an address by hand, the email syntax checker is free and needs no account.

Summary

  • Typos are valid syntax, so regex can't catch them; you need a domain-aware check.
  • Use fast mode's possible_typo reason and show suggestion as "Did you mean...?".
  • Ask, don't auto-correct. Check on blur, keep the key server-side, and fail open.

Fast mode is free on every plan, including the Free plan with 100 full checks a month. The request and response details are on the developers page.

Run these checks from your code

Verify addresses at signup, clean lists and read SPF/DKIM/DMARC with one API call. 100 free verifications a month, test keys that cost nothing, and the API on every plan.