Skip to main content

How to stop users creating multiple accounts with Gmail aliases

· 4 min read
Signitize
Email checks for signup forms

[email protected], [email protected] and [email protected] are three different strings and one inbox. If your signup treats them as three users, one person gets three free trials, three sets of credits or three referral bonuses.

The fix is not to block these addresses: real people use them. The fix is to know they belong to the same inbox.

Why one Gmail inbox has endless addresses​

Gmail delivers mail to the same inbox no matter how the address is written:

TrickExampleDelivered to
Dots[email protected][email protected]
+tag[email protected][email protected]
Old domain[email protected][email protected]
Upper case[email protected][email protected]

Combine them and a nine-letter username has thousands of spellings. Scripts that farm free tiers generate them automatically: johnsmith+1, johnsmith+2, and so on.

+tags (called subaddressing) are a legitimate feature. People use them to filter mail or to see who leaked their address. Rejecting every address with a + blocks honest users and does nothing against dots or googlemail.com.

The fix: a canonical email in a unique column​

Store two values for every user:

  • the address the user typed, for sending mail;
  • a canonical version, where every alias of one inbox gives the same value, with a unique constraint.
ALTER TABLE users ADD COLUMN email_canonical text UNIQUE;

At signup, compute the canonical email and look it up. If it exists, the person already has an account. Keep banned accounts (or at least their canonical email) instead of deleting them, and every alias of a banned inbox stays banned too.

Always send your verification email to the typed address. The canonical one is a duplicate-check key; for some providers it is not a deliverable mailbox.

Doing it yourself: the Gmail rule​

For Gmail alone the rule is short:

function canonicalGmail(email) {
const [local, domain] = email.trim().toLowerCase().split('@')
if (domain !== 'gmail.com' && domain !== 'googlemail.com') return email.trim().toLowerCase()
return `${local.split('+')[0].replaceAll('.', '')}@gmail.com`
}

canonicalGmail('[email protected]') // '[email protected]'

It gets harder from here, because every provider has different rules:

  • Outlook and Hotmail support +tags, but dots do matter: [email protected] and [email protected] are different people. Applying the Gmail rule to them merges strangers.
  • Yahoo uses -keyword instead of +tag for its "disposable addresses" ([email protected]), and the base name is not the login.
  • iCloud has three domains (icloud.com, me.com, mac.com) for the same inbox.
  • Proton ignores dots, hyphens and underscores, and has four domains.
  • Google Workspace custom domains ([email protected]) follow Gmail's +tag rule, but you only know a domain is on Google Workspace by looking up its MX records.
  • Every other domain decides for itself. On wikipedia.org, jo.hn+x may be a different mailbox, so the only safe change is lowercasing.

Get one of these wrong and you either let duplicates through or, worse, tell a real new user that they already have an account.

Doing it with one API call​

Signitize applies all of these rules and returns the canonical email, together with two other checks you want at signup anyway:

curl https://api.signitize.io/v1/check \
-H "Authorization: Bearer $SIGNITIZE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'
{ "email": "[email protected]", "disposable": false, "deliverable": true }

In Node.js:

const response = await fetch('https://api.signitize.io/v1/check', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNITIZE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: typedEmail }),
signal: AbortSignal.timeout(5000),
})
const { email } = await response.json()

if (await db.users.findOne({ emailCanonical: email })) {
throw new Error('You already have an account.')
}

The full rule table per provider is in Normalization.

Two mistakes to avoid​

Blocking the signup when the check fails. If the API (yours or ours) is slow, create the account as pending and check it later. Hold back the free credits until then. Never block a signup has the complete code.

Telling attackers which emails have accounts. "This email is already registered" lets anyone test addresses. For a consumer product, consider a neutral message ("Check your inbox to continue") and handle the duplicate by email instead.

Summary​

  • Don't block +tags or dots: real users rely on them.
  • Store a canonical email in a unique column and check it at signup.
  • Send mail to the typed address, never to the canonical one.
  • Keep banned users' canonical emails so aliases can't come back.

You can get a free API key (2,500 checks a month, no card) and test your own aliases in the Quickstart.