go 49 lines · 8 steps

Normalizing and deduping email addresses in Go

A small package that parses, validates, and canonicalizes email addresses, then collapses a list down to unique entries.

Explained by highlit
1package email
2 
3import (
4 "errors"
5 "net/mail"
6 "strings"
7)
8 
9var ErrInvalidAddress = errors.New("invalid email address")
10 
11func Normalize(raw string) (string, error) {
12 addr, err := mail.ParseAddress(strings.TrimSpace(raw))
13 if err != nil {
14 return "", ErrInvalidAddress
15 }
16 
17 at := strings.LastIndex(addr.Address, "@")
18 if at <= 0 || at == len(addr.Address)-1 {
19 return "", ErrInvalidAddress
20 }
21 
22 local := addr.Address[:at]
23 domain := strings.ToLower(addr.Address[at+1:])
24 
25 if strings.Contains(domain, "..") || strings.HasPrefix(domain, ".") {
26 return "", ErrInvalidAddress
27 }
28 
29 return local + "@" + domain, nil
30}
31 
32func NormalizeAll(raws []string) ([]string, error) {
33 seen := make(map[string]struct{}, len(raws))
34 out := make([]string, 0, len(raws))
35 
36 for _, raw := range raws {
37 norm, err := Normalize(raw)
38 if err != nil {
39 return nil, err
40 }
41 if _, dup := seen[norm]; dup {
42 continue
43 }
44 seen[norm] = struct{}{}
45 out = append(out, norm)
46 }
47 
48 return out, nil
49}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Lean on the standard library's mail.ParseAddress before layering on your own domain-specific checks.
  2. 2A single sentinel error lets callers compare with errors.Is instead of parsing message strings.
  3. 3A map with an empty-struct value is Go's idiomatic zero-cost set for deduplication.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Normalizing and deduping email addresses in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code