Sign-up Required
Compare email addresses and usernames in one canonical form
Store each email address and username as typed, plus one canonical key. Look up, compare and enforce uniqueness on the key only.
How to do it
For an email, trim spaces, apply Unicode NFC, lowercase the domain and convert it with IDNA under UTS #46. That is domainToASCII in Node.js and the idna package in Python, not Python's built-in idna codec, which follows the older IDNA 2003. Lowercase the local part too, because almost every mail provider ignores its case. Never remove dots or +tags, because only some providers ignore them. For a username, apply the PRECIS UsernameCaseMapped profile from RFC 8265, which folds case and width. Put the unique index on the key. Send mail to the address as typed. International addresses covers mailboxes with non-ASCII letters.
Example
import { domainToASCII } from 'node:url';
export function emailKey(typed) {
const address = typed.trim().normalize('NFC');
const at = address.lastIndexOf('@');
const local = address.slice(0, at).toLowerCase();
const domain = domainToASCII(address.slice(at + 1).toLowerCase());
return `${local}@${domain}`;
}
import unicodedata
import idna
def email_key(typed: str) -> str:
address = unicodedata.normalize("NFC", typed.strip())
local, _, domain = address.rpartition("@")
return f"{local.lower()}@{idna.encode(domain, uts46=True).decode('ascii')}"
CREATE UNIQUE INDEX users_email_key ON users (email_key);
How to check
Sign up as Anna@Example.com, then as anna@EXAMPLE.com. The second finds the first account. Sign up as anna+shop@example.com, and it creates a separate account.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/sign-up/identifier-normalisation.