Go library for canonicalizing email addresses to prevent duplicate accounts and registration spam.
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.comfoobar+trial1@gmail.comfoobar+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.
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) );
- 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.
- Google (
gmail.com,googlemail.com): mapsgooglemail.comtogmail.com, strips dots, and removes+tagsuffixes. Custom domains on Google Workspace preserve dots. - Proton (
proton.me,protonmail.com,protonmail.ch,pm.me): maps alias domains toproton.me, strips plus tags, and preserves dots. - Fastmail (over 100 domains): folds subdomain addressing (
alias@username.fastmail.comtousername@fastmail.com). - Apple iCloud (
icloud.com,me.com,mac.com): mapsme.comandmac.comtoicloud.com, and strips plus tags. - Yandex (
yandex.ru,ya.ru, and country TLDs): maps regional domains toyandex.ru, strips plus tags, and converts hyphens to dots (first-lasttofirst.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.
go get github.com/fumbledlol/emailnormpackage 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
}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| 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 |
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