Lookalike and punycode email domains: spotting homographs at sign-up

Lookalike domains imitate gmail.com or paypal.com with Cyrillic letters, digits or rn for m. How punycode exposes them and how isBusinessEmail flags them.

  • #fraud
  • #signup
  • #dns
  • #engineering
Two domains that look identical, gmail.com and gmаil.com with a Cyrillic а; the second unfolds into its punycode form xn--gmil-63d.com and gets a red lookalike chip. // same pixels, different domains gmail.com U+0061 Latin a gmаil.com U+0430 Cyrillic а punycode (what DNS and mail servers see) xn--gmil-63d.com lookalike // looks like gmail.com, isn't gmail.com

A lookalike domain imitates a well-known domain with characters that look the same: gmаil.com with a Cyrillic а, g00gle.com with zeros, rnicrosoft.com with rn standing in for m. Domains with non-ASCII letters travel as punycode, so the Cyrillic version reaches your server as xn--gmil-63d.com, a different domain entirely. isBusinessEmail flags lookalikes of popular email providers and a few big brands with is_lookalike; it doesn’t check lookalikes of your own brand, and this post shows how to add that yourself.

Four ways to make one domain look like another

Letters from other scripts

Cyrillic and Greek both have letters that look exactly like Latin ones in most fonts. An internationalized domain name can contain them, so gmаil.com with a Cyrillic а (U+0430) is a valid, registrable domain that renders the same as gmail.com. This is the IDN homograph trick.

Looks like Actually Unicode
a Cyrillic small a, а U+0430
o Cyrillic small o, о U+043E
e Cyrillic small ie, е U+0435
p Cyrillic small er, р U+0440
o Greek small omicron, ο U+03BF

Mixing one foreign letter into a Latin name is the common case, but a name can be built entirely from Cyrillic letters. A widely shared 2017 demonstration registered аррӏе.com, all Cyrillic, which several browsers displayed exactly like apple.com. Browsers have since tightened the rules for when they show a domain in Unicode and when they fall back to punycode. Sign-up forms, admin panels, CRMs and email clients don’t all apply the same rules.

Digit swaps

0 for o, 1 for l, 3 for e, 5 for s: g00gle.com, paypa1.com, micr0soft.com. These are plain ASCII, so no punycode is involved, and in many sans-serif fonts a 1 and an l are hard to tell apart.

Letter pairs

rn reads as m at small sizes, and vv reads as w: rnicrosoft.com, arnazon.com, vveb.de.

Accents

gmáil.com and hotmàil.com add an accent most readers skip over. They are internationalized domains too, so they travel as punycode: xn--gmil-6na.com and xn--hotmil-lta.com.

How punycode exposes them

DNS and mail servers work in ASCII. An internationalized domain is converted label by label: any label with a non-ASCII character becomes xn-- followed by its punycode encoding (RFC 3492). The legitimate bücher.de becomes xn--bcher-kva.de. The impostor gmаil.com becomes xn--gmil-63d.com: the remaining ASCII letters gmil come first, then a hyphen, then 63d, which encodes which character goes where.

Software that compares strings sees the difference at once. People don’t, and depending on where the address came from, your server may receive either form. So normalize before you compare anything. isBusinessEmail lowercases the address, applies Unicode normalization (NFKC) and converts the domain to punycode, and both normalized_email and domain show the xn-- form. The email field echoes what you sent. Two side effects worth knowing:

  • jane@gmаil.com and jane@xn--gmil-63d.com normalize to the same address, so use normalized_email to de-duplicate sign-ups.
  • NFKC folds compatibility characters such as fullwidth letters before anything else, so gmail.com simply becomes gmail.com, which is classified as Gmail.

A trimmed response for jane@gmаil.com, with the Cyrillic а exactly as sent:

{
  "email": "jane@gmаil.com",
  "normalized_email": "jane@xn--gmil-63d.com",
  "domain": "xn--gmil-63d.com",
  "category": "unknown",
  "recommendation": "review",
  "is_lookalike": true,
  "did_you_mean": "gmail.com",
  "reasons": ["lookalike_domain", "custom_domain", "spf_present"]
}

The category depends on what the domain’s DNS shows. Here the domain has mail servers but little else, so it lands in unknown. A lookalike that doesn’t exist comes back invalid and is blocked under every policy anyway.

How isBusinessEmail detects lookalikes

Fold the domain into a skeleton

The engine reduces the domain to a skeleton, the plain Latin string a reader would think they see:

  1. Decode any xn-- labels back to Unicode.
  2. Decompose the characters (NFKD) and drop accents, so á becomes a.
  3. Replace confusable characters with the Latin letters they imitate.
  4. Replace rn with m and vv with w.
Characters Fold to
Cyrillic а е о р с у х і ј ѕ ԁ һ ԛ ԝ ӏ a e o p c y x i j s d h q w l
Greek ο α ν ρ τ ι κ ε υ o a v p t i k e u
Latin lookalikes ɡ ı ʟ g i l
Digits 0 1 3 5 o l e s
Pairs rn, vv m, w

Compare it with the targets

The targets are the popular email providers used for typo suggestions (gmail.com, outlook.com, yahoo.com, icloud.com, proton.me, gmx.de, web.de and others) plus five brands that get imitated a lot: google.com, microsoft.com, apple.com, paypal.com and amazon.com. If the domain isn’t a target itself but its skeleton equals a target’s skeleton, it’s a lookalike, and did_you_mean names the domain it imitates.

Domain Skeleton Imitates
gmаil.com (xn--gmil-63d.com) gmail.com gmail.com
gmáil.com (xn--gmil-6na.com) gmail.com gmail.com
аррӏе.com (xn--80ak6aa92e.com) apple.com apple.com
g00gle.com google.com google.com
paypa1.com paypal.com paypal.com
rnicrosoft.com microsoft.com microsoft.com
vveb.de web.de web.de

Lists first, then typos, then lookalikes

A domain already on a list keeps that verdict. Otherwise the typo check runs first, and a domain one edit from a provider is reported as a typo: gmai1.com gets typo_suspected and did_you_mean: "gmail.com", not the lookalike flag. The lookalike check catches what edit distance can’t: a single Cyrillic letter turns the whole label into xn--…, many edits away from the original, and the five brands aren’t on the typo list at all.

Real organizations are cleared

The flag is removed when the domain’s DNS shows strong evidence of a real organization: Google Workspace or Microsoft 365 mail, an email security gateway, business SaaS verification tokens in TXT, a Microsoft Entra tenant, or business senders in SPF with an enforcing DMARC policy. The same rule clears typo suspicions.

What the policies do

Lookalike domain comes back as b2b strict lenient
business or unknown review block review
invalid or disposable block block block

lenient ignores typos but not lookalikes, which is why a lookalike gets review even there. Categories and policies has the full table, and reason codes describes lookalike_domain.

Why lookalikes show up at sign-up

Nobody types a Cyrillic letter by accident. A lookalike at sign-up is usually one of these:

  • Impersonation. An account on paypa1.com or micr0soft.com looks official in your admin panel, in team invites, in shared documents and in notifications your product sends on the user’s behalf. Messages from a legitimate service carry that service’s reputation, which is exactly what a phisher wants.
  • Getting past blocklists. A rule that blocks gmail.com by exact string match lets gmаil.com through. So does a one-trial-per-domain limit, because every lookalike is a fresh domain.
  • Setting up phishing. Accounts on lookalike domains can be used to verify sender identities, host forms or collect replies that target the imitated brand’s users.

Weigh a lookalike together with other signals. Domain age helps: with deep=true, which counts as five checks and is limited to 1,000 deep checks a day, the response includes domain_age_days and flags domains registered in the last 30 days. Domain age as a fraud signal and free trial abuse email signals cover how to combine them.

Typo or lookalike: different handling

Both fill did_you_mean, but they need different treatment. A typo such as gmial.com is a mistake, and the right answer is a one-click “Did you mean jane@gmail.com?”, as in email typo domains. A lookalike is almost never a mistake, so don’t offer its did_you_mean as a fix. Decide on the server:

const verdict = await checkWorkEmail(email); // secret key, fails open
if (verdict.is_lookalike) {
  await flagForReview(account, { reason: 'lookalike_domain', imitates: verdict.did_you_mean });
}

Store normalized_email, not the Unicode input, so the same lookalike can’t sign up twice in two spellings.

What it doesn’t detect: your own brand

isBusinessEmail checks lookalikes of popular email providers and of google.com, microsoft.com, apple.com, paypal.com and amazon.com. It does not know your brand, your customers’ domains or arbitrary company names. A sign-up from acrne.io imitating acme.io comes back as an ordinary domain.

If impersonating your own company is a risk, for example in invites or support conversations, compare skeletons yourself. This Node.js snippet is a starting point, not a complete confusables table:

import { domainToUnicode } from 'node:url';

// A starting point, not a complete table: Unicode publishes the full list of
// confusable characters (UTS #39). Extend it and test it on your own sign-ups.
const CONFUSABLES = {
  '\u0430': 'a', '\u0435': 'e', '\u043e': 'o', '\u0440': 'p', // Cyrillic а е о р
  '\u0441': 'c', '\u0443': 'y', '\u0445': 'x', '\u0456': 'i', // Cyrillic с у х і
  '\u03b1': 'a', '\u03b5': 'e', '\u03bf': 'o', '\u03c1': 'p', // Greek α ε ο ρ
  0: 'o', 1: 'l', 3: 'e', 5: 's',
};

function skeleton(domain) {
  const unicode = domainToUnicode(domain.toLowerCase()); // xn--… back to letters
  return [...unicode.normalize('NFKD').replace(/[\u0300-\u036f]/g, '')] // drop accents
    .map((ch) => CONFUSABLES[ch] ?? ch)
    .join('')
    .replace(/rn/g, 'm')
    .replace(/vv/g, 'w');
}

// The domains you want to protect.
const OURS = ['acme.io', 'acme.com'];
const oursBySkeleton = new Map(OURS.map((d) => [skeleton(d), d]));

function imitatesUs(domain) {
  if (OURS.includes(domain)) return null;
  return oursBySkeleton.get(skeleton(domain)) ?? null;
}

imitatesUs('xn--cme-5cd.io'); // 'acme.io': Cyrillic а
imitatesUs('acrne.io');       // 'acme.io': rn for m
imitatesUs('acm3.com');       // 'acme.com': 3 for e
imitatesUs('acme.co');        // null: same name, other ending

Pass it the domain field from the API response, which is already normalized. Exact skeleton matches miss other endings (acme.co) and names that only contain your brand (acme-support.io), so add those checks if they matter to you, and test against your real sign-ups before you block anything.

Next steps

  1. Read is_lookalike, did_you_mean and normalized_email in response fields.
  2. Handle typos separately with email typo domains.
  3. Combine lookalikes with other abuse signals using the free trial abuse playbook.
  4. Get a free API key and try a lookalike against your own sign-up flow.

Frequently asked questions

What is a punycode email domain?

Punycode is the ASCII form of an internationalized domain name. A domain with non-ASCII letters, such as bücher.de, travels through DNS and mail systems as xn--bcher-kva.de. Any part of a domain that starts with xn-- contains at least one character outside plain ASCII.

What is a homograph email address?

An address whose domain looks identical to a familiar one but uses different characters, for example gmail.com written with a Cyrillic а instead of a Latin a. People see gmail.com; mail servers see xn--gmil-63d.com, a different domain registered by someone else.

Are all punycode domains suspicious?

No. Many legitimate domains use non-Latin scripts or accented letters, in Chinese, Arabic, Cyrillic, German and many other languages. A domain is suspicious when it is built to look like a specific well-known domain, not because it contains xn--.

Does isBusinessEmail detect lookalikes of my company's domain?

No. It compares domains with popular email providers and five big brands: google.com, microsoft.com, apple.com, paypal.com and amazon.com. To catch lookalikes of your own brand, compare a skeleton of each sign-up domain with a skeleton of your own domain in your code.

What is the difference between a typo domain and a lookalike domain?

A typo is a keyboard slip, such as gmial.com, and deserves a one-click fix. A lookalike is built to look identical, such as g00gle.com or gmail.com with a Cyrillic letter, and usually means impersonation, so it deserves review rather than a suggestion.