Understanding Go's net/mail.Address and Parser 1/3
Hey there! Today we’re going to dive into Go’s net/mail package, focusing on how it handles email addresses and parsing. If you’ve ever needed to work with email addresses in Go, you’ve probably encountered this package. Let’s break it down and see how to use it effectively.
Getting Started with mail.Address
The mail.Address type is the foundation of email address handling in Go. It’s a simple struct with two fields:
package main
import (
"fmt"
"net/mail"
)
func main() {
// Creating a mail.Address directly
addr := &mail.Address{
Name: "John Smith",
Address: "[email protected]",
}
fmt.Printf("Name: %s\nEmail: %s\n", addr.Name, addr.Address)
// String representation
fmt.Printf("Full address: %s\n", addr.String())
}
Running this will give you a properly formatted email address string. The String() method handles all the necessary quoting and escaping for you.
Parsing Email Addresses
Let’s look at how to parse email addresses. There are a few ways to do this:
func parseExamples() error {
// Simple case
addr, err := mail.ParseAddress("[email protected]")
if err != nil {
return fmt.Errorf("parsing simple address: %w", err)
}
// With display name
addr2, err := mail.ParseAddress("John Smith <[email protected]>")
if err != nil {
return fmt.Errorf("parsing address with name: %w", err)
}
// With quoted name containing special characters
addr3, err := mail.ParseAddress(`"Smith, John" <[email protected]>`)
if err != nil {
return fmt.Errorf("parsing quoted name: %w", err)
}
fmt.Printf("Simple: %v\n", addr)
fmt.Printf("With name: %v\n", addr2)
fmt.Printf("Quoted name: %v\n", addr3)
return nil
}
The parser is pretty forgiving and handles various formats, but it’s good to know what it expects:
- Bare email addresses: [email protected]
- Named addresses: John Smith <[email protected]>
- Quoted names: "Smith, John" <[email protected]>
Working with Address Lists
Often you’ll need to handle multiple addresses, like in To:, Cc:, or Bcc: fields. That’s where ParseAddressList comes in:
func handleAddressList() error {
// Common email header format
list := `John Smith <[email protected]>, Alice <[email protected]>, "Bob, Jr" <[email protected]>`
addresses, err := mail.ParseAddressList(list)
if err != nil {
return fmt.Errorf("parsing address list: %w", err)
}
for i, addr := range addresses {
fmt.Printf("Address %d:\n", i+1)
fmt.Printf(" Name: %q\n", addr.Name)
fmt.Printf(" Email: %s\n", addr.Address)
}
return nil
}
Real-World Examples
Let’s look at some practical applications.
1. Email Address Validator
Here’s a robust email validator that uses net/mail:
type ValidationResult struct {
Address string
IsValid bool
ErrorDetail string
}
func validateEmails(addresses []string) []ValidationResult {
results := make([]ValidationResult, len(addresses))
for i, addr := range addresses {
result := ValidationResult{
Address: addr,
}
// Try parsing
parsed, err := mail.ParseAddress(addr)
if err != nil {
result.IsValid = false
result.ErrorDetail = err.Error()
} else {
result.IsValid = true
// Use the normalized address
result.Address = parsed.Address
}
results[i] = result
}
return results
}
// Usage example
func demoValidation() {
addresses := []string{
"[email protected]",
"John Smith <[email protected]>",
"invalid@[email protected]",
`"Smith, John" <[email protected]>`,
"no-at-sign.example.com",
}
results := validateEmails(addresses)
for _, r := range results {
if r.IsValid {
fmt.Printf("✓ %s is valid\n", r.Address)
} else {
fmt.Printf("✗ %s is invalid: %s\n", r.Address, r.ErrorDetail)
}
}
}
2. Address List Normalizer
When working with email systems, you often need to normalize address lists:
type AddressNormalizer struct {
lowercase bool
sort bool
}
func NewAddressNormalizer(lowercase, sort bool) *AddressNormalizer {
return &AddressNormalizer{
lowercase: lowercase,
sort: sort,
}
}
func (n *AddressNormalizer) NormalizeList(input string) (string, error) {
// Parse the address list
addresses, err := mail.ParseAddressList(input)
if err != nil {
return "", fmt.Errorf("parsing address list: %w", err)
}
// Normalize each address
for i, addr := range addresses {
if n.lowercase {
addr.Address = strings.ToLower(addr.Address)
}
addresses[i] = addr
}
// Sort if requested
if n.sort {
sort.Slice(addresses, func(i, j int) bool {
return addresses[i].Address < addresses[j].Address
})
}
// Convert back to string
var parts []string
for _, addr := range addresses {
parts = append(parts, addr.String())
}
return strings.Join(parts, ", "), nil
}
3. Header Address Extractor
When working with email headers, you often need to extract addresses from various fields:
type EmailAddresses struct {
From *mail.Address
To []*mail.Address
Cc []*mail.Address
ReplyTo []*mail.Address
}
func ExtractAddresses(headers map[string][]string) (EmailAddresses, error) {
var result EmailAddresses
var err error
// Handle From (should be single address)
if from := headers["From"]; len(from) > 0 {
result.From, err = mail.ParseAddress(from[0])
if err != nil {
return result, fmt.Errorf("parsing From address: %w", err)
}
}
// Handle address lists
if to := headers["To"]; len(to) > 0 {
result.To, err = mail.ParseAddressList(to[0])
if err != nil {
return result, fmt.Errorf("parsing To addresses: %w", err)
}
}
if cc := headers["Cc"]; len(cc) > 0 {
result.Cc, err = mail.ParseAddressList(cc[0])
if err != nil {
return result, fmt.Errorf("parsing Cc addresses: %w", err)
}
}
if replyTo := headers["Reply-To"]; len(replyTo) > 0 {
result.ReplyTo, err = mail.ParseAddressList(replyTo[0])
if err != nil {
return result, fmt.Errorf("parsing Reply-To addresses: %w", err)
}
}
return result, nil
}
Best Practices
Always Use ParseAddress for Validation
// Don't just check for @ symbol func isValidEmail(email string) bool { _, err := mail.ParseAddress(email) return err == nil }
Handle Encoding Properly
// When displaying names, remember they might contain UTF-8 func formatAddress(addr *mail.Address) string { if addr.Name != "" { return fmt.Sprintf("%s <%s>", addr.Name, addr.Address) } return addr.Address }
Use String() Method
// Let the package handle proper quoting addr := &mail.Address{ Name: "Smith, John", Address: "[email protected]", } formatted := addr.String() // Will properly quote the name
Common Pitfalls
- Assuming Single Address in From Field
```go
// Wrong
from, _ := mail.ParseAddressList(headers.Get(“From”))
// Right from, _ := mail.ParseAddress(headers.Get(“From”))
2. **Not Handling Empty Lists**
```go
func safeParseList(input string) []*mail.Address {
if input == "" {
return nil
}
addresses, err := mail.ParseAddressList(input)
if err != nil {
return nil
}
return addresses
}
- Forgetting About Quotes
```go
// This might not work as expected
name := “Smith, John”
email := “[email protected]”
raw := fmt.Sprintf(“%s <%s>“, name, email)
// Better approach addr := &mail.Address{ Name: name, Address: email, } raw = addr.String() ```
What’s Next?
In our next article, we’ll dive into message handling with net/mail, including how to parse and work with entire email messages, not just addresses. We’ll see how to handle headers, bodies, and more complex email structures.
Until then, keep experimenting with the address parser! It’s a fundamental building block for any email-handling code in Go.