Gmail dots and plus aliases: how to catch duplicate signups
j.doe+7@gmail.com and jdoe@gmail.com are the same inbox. How Gmail addressing works, a normalization function in JavaScript and Python, and the edge cases that bite.
One inbox, endless addresses
Gmail has two addressing rules that make it trivial to create many accounts from one inbox:
- Dots are ignored. For
@gmail.comaddresses,jane.doe,janedoeandj.a.n.e.d.o.eare the same mailbox. A ten-letter name has hundreds of dotted variants. - Anything after a plus sign is ignored.
janedoe+trial1@gmail.com,janedoe+trial2@gmail.comand so on all deliver tojanedoe@gmail.com. The number of variants is unlimited.
On top of that, @googlemail.com is the same service as @gmail.com. Every variant passes e-mail verification, because the confirmation mail really does arrive. To your signup form they are all different, brand-new users.
Plus addressing is not Gmail-only: Fastmail and Proton Mail support it too, as do many company mail servers. Dot-insensitivity, on the other hand, is specific to consumer Gmail.
Normalize, then compare
The fix is to store a normalized form of each address next to the original, and check new signups against it. Keep the original for sending mail — the user typed it, and some people rely on their plus tags to filter mail.
// JavaScript function normalizeEmail(input) { const raw = input.trim().toLowerCase(); const at = raw.lastIndexOf("@"); if (at < 1) return null; let local = raw.slice(0, at); let domain = raw.slice(at + 1).replace(/\.$/, ""); if (domain === "googlemail.com") domain = "gmail.com"; const plus = local.indexOf("+"); if (plus > 0) local = local.slice(0, plus); if (domain === "gmail.com") local = local.replaceAll(".", ""); return `${local}@${domain}`; } normalizeEmail("J.Doe+trial7@GoogleMail.com"); // "jdoe@gmail.com"
# Python def normalize_email(value: str) -> str | None: raw = value.strip().lower() local, sep, domain = raw.rpartition("@") if not sep or not local: return None domain = domain.rstrip(".") if domain == "googlemail.com": domain = "gmail.com" base = local.split("+", 1)[0] or local if domain == "gmail.com": base = base.replace(".", "") return f"{base}@{domain}"
Then add a column and an index, and look it up at signup:
ALTER TABLE users ADD COLUMN email_normalized text; CREATE INDEX users_email_normalized ON users (email_normalized); -- at signup SELECT count(*) FROM users WHERE email_normalized = $1;
Edge cases that bite
- Don't strip dots outside consumer Gmail. On most providers, and on Google Workspace company domains,
jane.doe@andjanedoe@are different people. Removing dots there merges strangers. - Plus-stripping is a heuristic elsewhere. On nearly every provider a plus tag is an alias, but a mail server is free to treat
+as an ordinary character. Treat a match on a non-Gmail domain as a signal, not proof. - Don't reject the plus tag itself. Many careful people use
name+service@gmail.comfor every site they join. A plus tag on a new mailbox is normal; a plus tag on a mailbox that already has an account is the signal. - Lowercase before comparing, and trim whitespace and trailing dots. Mail providers treat the local part case-insensitively in practice.
- Other providers have their own alias schemes (Yahoo's disposable addresses, Outlook aliases on the same account). You won't catch these by normalization — this is where device and network signals take over.
What to do on a match
A normalized match means the new account receives mail in an inbox that already has an account with you. Usually the right response is not an error but a nudge: “You already have an account with this e-mail — sign in instead?”. For free trials and free credits, it is also a strong reason to skip the trial or send the signup to review. See How to prevent free trial abuse for a full decision flow.
How Attrelix handles it
Every check normalizes the address (Gmail dots, plus tags, googlemail.com and Proton's domains), looks for existing accounts with the same mailbox, and adds the alias_of_existing reason when it finds one. The original address is never altered in your data, and the linked accounts are returned in the response so you can see the whole cluster.
Summary
- Gmail ignores dots and plus tags; many providers ignore plus tags.
- Store a normalized address next to the original and index it.
- Strip dots only for consumer Gmail; treat other matches as signals.
- On a match, suggest signing in, and don't hand out a second trial.