Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

emailnorm

Go Reference License: MIT

Go library for canonicalizing email addresses to prevent duplicate accounts and registration spam.

Why normalization matters

Most web services store email addresses as raw, lowercased strings. Spammers exploit provider-specific routing quirks to register multiple accounts that route into one mailbox:

  • f.o.o.b.a.r@gmail.com
  • foobar+trial1@gmail.com
  • foobar+promo@googlemail.com

To a standard database query, these look like distinct users. To Google, every one of these delivers to foobar@gmail.com.

Naive normalizers often cause serious deliverability bugs by over-stripping:

  • Stripping dots on Google Workspace or Fastmail can collide separate coworkers.
  • Stripping plus signs on Yahoo, Tuta, GMX, Web.de, Naver, QQ Mail, or Brave aliases breaks deliverability because those services reject sub-addressing (550 User not found).
  • Merging Hotmail and Outlook causes collisions because Microsoft allows different people to own the same username across different domains.

emailnorm encodes verified routing rules for major email providers without collapsing distinct mailboxes.

Recommended storage pattern

When using emailnorm for user accounts, store both the raw email and the normalized email in separate database columns:

sql CREATE TABLE users ( id UUID PRIMARY KEY, email TEXT NOT NULL, -- Raw email as entered (e.g., Jane.Doe+receipts@googlemail.com) normalized_email TEXT NOT NULL UNIQUE -- Canonical email (e.g., janedoe@gmail.com) );

Why store both?

  • Outbound delivery: Send all transactional and marketing emails to the raw address. Users often configure mail filters based on their aliases (such as +news or +invoices) and expect mail to arrive at the address they entered.
  • User preference: Preserves the user's preferred casing and domain alias in the interface.
  • Deduplication and security: Use ormalized_email for unique constraints and login lookup queries. This prevents malicious or accidental duplicate registrations, multiple trial abuse, and referral fraud.

Supported providers

  • Google (gmail.com, googlemail.com): maps googlemail.com to gmail.com, strips dots, and removes +tag suffixes. Custom domains on Google Workspace preserve dots.
  • Proton (proton.me, protonmail.com, protonmail.ch, pm.me): maps alias domains to proton.me, strips plus tags, and preserves dots.
  • Fastmail (over 100 domains): folds subdomain addressing (alias@username.fastmail.com to username@fastmail.com).
  • Apple iCloud (icloud.com, me.com, mac.com): maps me.com and mac.com to icloud.com, and strips plus tags.
  • Yandex (yandex.ru, ya.ru, and country TLDs): maps regional domains to yandex.ru, strips plus tags, and converts hyphens to dots (first-last to first.last).
  • Microsoft (outlook.com, hotmail.com, live.com, msn.com): strips plus tags while keeping separate domains distinct.
  • Mail.ru (mail.ru, bk.ru, inbox.ru, list.ru, internet.ru): strips plus tags while keeping separate domains distinct.
  • Zoho Mail and DuckDuckGo: strips plus tags and preserves dots.
  • Yahoo, Tuta, GMX, Web.de, Naver, Daum, QQ, NetEase, and Brave: keeps literal addresses without plus stripping so outgoing mail does not bounce.
  • Internationalized domains: normalizes Unicode to NFC and converts non-ASCII domain names to Punycode.

Installation

go get github.com/fumbledlol/emailnorm

Usage

Normalizing email addresses

package main

import (
	"fmt"

	"github.com/fumbledlol/emailnorm"
)

func main() {
	// Gmail: removes dots and plus tags, unifies googlemail
	fmt.Println(emailnorm.Normalize("  John.Doe+promo@googlemail.com "))
	// Output: johndoe@gmail.com

	// Proton: maps alias domains to proton.me, removes plus tag
	fmt.Println(emailnorm.Normalize("user+secret@protonmail.ch"))
	// Output: user@proton.me

	// Yandex: maps regional domain, unifies hyphens and dots
	fmt.Println(emailnorm.Normalize("first-last+tag@yandex.kz"))
	// Output: first.last@yandex.ru

	// Fastmail: folds subdomain addressing
	fmt.Println(emailnorm.Normalize("shopping@myaccount.fastmail.com"))
	// Output: myaccount@fastmail.com

	// GMX, Naver, Brave: keeps plus signs to prevent delivery failure
	fmt.Println(emailnorm.Normalize("user+tag@gmx.de"))
	// Output: user+tag@gmx.de

	// Internationalized domain names: converts to ASCII Punycode
	fmt.Println(emailnorm.Normalize("contact@München.de"))
	// Output: contact@xn--mnchen-3ya.de
}

Inspecting provider rules

provider := emailnorm.DetectProvider("user@gmx.de")
fmt.Println(provider)                           // GMX
fmt.Println(provider.SupportsPlusAddressing())   // false
fmt.Println(provider.StripsDots())               // false

gmail := emailnorm.DetectProvider("user@gmail.com")
fmt.Println(gmail.StripsDots())                  // true

Normalization rules

Provider family Strip dots Strip plus tag Domain folding Example input Normalized output
Gmail Yes Yes googlemail.com -> gmail.com f.o.o+bar@googlemail.com foo@gmail.com
Proton No Yes protonmail.*, pm.me -> proton.me user.name+ref@protonmail.com user.name@proton.me
Fastmail No Yes Subdomain fold (*@user.<dom> -> user@<dom>) newsletter@john.fastmail.com john@fastmail.com
iCloud No Yes me.com, mac.com -> icloud.com user+tag@me.com user@icloud.com
Yandex No (- -> .) Yes ya.ru, yandex.* -> yandex.ru first-last+tag@ya.ru first.last@yandex.ru
Outlook / Hotmail No Yes Distinct domains john.doe+trial@outlook.com john.doe@outlook.com
Mail.ru No Yes Distinct domains user+tag@mail.ru user@mail.ru
Zoho Mail No Yes Distinct domains user+tag@zoho.com user@zoho.com
DuckDuckGo No Yes Distinct domains user+tag@duck.com user@duck.com
Brave aliases No No Literal routing user+tag@bravealias.com user+tag@bravealias.com
Yahoo No No Literal routing user+tag@yahoo.com user+tag@yahoo.com
Tuta No No Literal routing user+tag@tutanota.com user+tag@tutanota.com
GMX / Web.de No No Literal routing user+tag@gmx.de user+tag@gmx.de
Naver / Daum No No Literal routing user+tag@naver.com user+tag@naver.com
QQ / NetEase No No Literal routing 123456+tag@qq.com 123456+tag@qq.com
Custom / Generic No Yes Literal domain dev+test@example.org dev@example.org

Benchmarks

BenchmarkNormalize_Gmail-12       2113540    565.2 ns/op    112 B/op    4 allocs/op
BenchmarkNormalize_Proton-12      3206744    380.1 ns/op     56 B/op    2 allocs/op
BenchmarkNormalize_Fastmail-12    2263084    540.5 ns/op     72 B/op    3 allocs/op
BenchmarkNormalize_Yandex-12      3018289    407.6 ns/op     72 B/op    3 allocs/op

License

MIT

About

Normalize email addresses across Gmail, Outlook, Yahoo, and Fastmail to stop duplicate signups

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages