email

package module
v0.0.0-...-4d400a0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 8 Imported by: 0

README

email-address-validator

Go Reference CI

Package email checks an address against the Mailbox grammar of RFC 5321, the production an address must satisfy to appear in an SMTP MAIL or RCPT command. Validators commonly approximate this grammar with a regular expression; this package implements it directly, along with the length limits and errata that accompany it.

What it checks

  • The RFC 5321 Mailbox grammar: dot-string and quoted-string local parts, letter-digit-hyphen domain labels, and IPv4 and IPv6 address literals.
  • Length limits from RFC 5321 section 4.5.3.1 as corrected by RFC 3696 erratum 1690: a 64-octet local part, a 255-octet domain, and a 254-octet address.
  • With ValidateSMTPUTF8, the internationalization extensions of RFC 6531 and RFC 6532: well-formed UTF-8 in the local part and IDNA2008 U-labels in the domain, including the RFC 5892 derived-property and contextual rules.

Validation is syntactic. The package does not resolve DNS, apply IDN registry policy, or confirm that a mailbox exists. SPEC.md records the governing specifications, the rules enforced, and the judgment calls behind them.

Install

go get github.com/initialcapacity/email-address-validator

The import path ends in email-address-validator; the package name is email:

import email "github.com/initialcapacity/email-address-validator"

Usage

Validate and ValidateSMTPUTF8 return nil for a valid address. IsValid and IsValidSMTPUTF8 are boolean forms of the same checks.

if err := email.Validate("grace.hopper@example.com"); err != nil {
    log.Fatal(err)
}

email.IsValid("用户@example.com")          // false: non-ASCII requires SMTPUTF8
email.IsValidSMTPUTF8("用户@example.com") // true

Every failure is a *email.SyntaxError wrapping a sentinel error. Classify failures with errors.Is, then recover the byte offset of the fault with errors.As:

err := email.Validate("ada@exa_mple.com")

if errors.Is(err, email.ErrInvalidDomain) {
    var syntaxErr *email.SyntaxError
    if errors.As(err, &syntaxErr) {
        fmt.Println(syntaxErr.Offset) // 7, the underscore
    }
}

The package documentation lists the sentinel errors and holds runnable examples.

Reimplementing in another language

The repository doubles as a porting kit. REIMPLEMENTING.md lists what a port needs, and testdata/cases.json holds a language-agnostic conformance suite that CI runs against this implementation.

To start a port, give a coding agent this prompt, filling in the language:

Reimplement the email address validator from https://github.com/initialcapacity/email-address-validator in <language>. Clone or fetch the repository, then read REIMPLEMENTING.md, SPEC.md, and testdata/README.md, in that order, along with the RFC sections they cite. The Go source is the reference implementation.

Implement both validation modes: strict RFC 5321 (ASCII) and RFC 6531 (SMTPUTF8). Expose a boolean check and a diagnostic form that reports an error kind and a byte offset. Accept byte-string input so the base64-encoded cases run.

Write a conformance test that runs every case in testdata/cases.json through both modes, and iterate until both tiers pass in full: the accept/reject results must match valid.ascii and valid.smtputf8, and the diagnostics must match every errors entry, kind and offset. Fix the implementation, never the expectations, and do not special-case any vector. Use idiomatic tooling and project structure for <language>, and keep third-party dependencies to what the standard library cannot provide.

Development

Build and test with the standard Go toolchain, version 1.27 or later:

go build ./...
go test -race ./...

Before sending a change, run the same checks CI runs:

test -z "$(gofmt -l .)"
go vet ./...
go test -race ./...

unicode_tables.go holds case folding, Joining_Type, and virama data generated from the Unicode Character Database. A test fails when its version drifts from the standard library's; after a toolchain upgrade, regenerate it:

go run gen_unicode_tables.go -ucd /path/to/ucd -version 17.0.0

A fuzz target exercises the parser with arbitrary input and asserts that RFC 5321 validity implies RFC 6531 validity. CI runs it for 30 seconds; run it longer when changing the parser:

go test -fuzz=FuzzValidate -fuzztime=5m .

License

MIT; see LICENSE.

Documentation

Overview

Package email validates email addresses against the IETF specifications that define them.

An address is checked against the Mailbox production of RFC 5321 (Simple Mail Transfer Protocol), section 4.1.2, which is the grammar an address must satisfy to be usable in a MAIL or RCPT command:

Mailbox    = Local-part "@" ( Domain / address-literal )
Local-part = Dot-string / Quoted-string

Length limits come from RFC 5321 section 4.5.3.1 (64-octet local part, 255-octet domain, 256-octet path) as clarified by RFC 3696 erratum 1690: an address can be at most 254 octets. Domain labels are limited to 63 octets per RFC 1035.

ValidateSMTPUTF8 additionally applies the internationalization extensions of RFC 6531 and RFC 6532, which extend atext and qtextSMTP with well-formed non-ASCII UTF-8 and permit IDNA2008 U-labels in the domain.

RFC 5322's addr-spec features that exist only for message framing (comments, folding white space, obsolete syntax) are intentionally not accepted: they are not part of a mailbox address, and RFC 5322 itself says they SHOULD NOT be used in addr-spec.

Each validation failure is reported as a *SyntaxError, which records the byte offset of the fault and wraps one of the package's sentinel errors, so callers can classify failures with errors.Is. All functions are safe for concurrent use.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrEmptyAddress     = errors.New("email address is empty")
	ErrAddressTooLong   = errors.New("email address exceeds 254 octets (RFC 5321 4.5.3.1.3, RFC 3696 erratum 1690)")
	ErrMissingAtSign    = errors.New(`email address has no "@" separating local part and domain`)
	ErrLocalPartTooLong = errors.New("local part exceeds 64 octets (RFC 5321 4.5.3.1.1)")
	ErrInvalidLocalPart = errors.New("invalid local part")
	ErrInvalidDomain    = errors.New("invalid domain")
)

Errors reported by Validate and ValidateSMTPUTF8. Errors returned by this package wrap one of these sentinels, so callers can classify failures with errors.Is.

Functions

func IsValid

func IsValid(addr string) bool

IsValid reports whether addr is valid per RFC 5321.

Example
package main

import (
	"fmt"

	email "github.com/initialcapacity/email-address-validator"
)

func main() {
	fmt.Println(email.IsValid("ada@example.com"))
	fmt.Println(email.IsValid("ada@@example.com"))
}
Output:
true
false

func IsValidSMTPUTF8

func IsValidSMTPUTF8(addr string) bool

IsValidSMTPUTF8 reports whether addr is valid per RFC 6531.

Example
package main

import (
	"fmt"

	email "github.com/initialcapacity/email-address-validator"
)

func main() {
	fmt.Println(email.IsValidSMTPUTF8("用户@example.com"))
	fmt.Println(email.IsValidSMTPUTF8("用户@exa_mple.com"))
}
Output:
true
false

func Validate

func Validate(addr string) error

Validate reports whether addr is a syntactically valid email address per RFC 5321 (ASCII only). It returns nil for a valid address and a *SyntaxError wrapping one of this package's sentinel errors otherwise.

Example
package main

import (
	"fmt"

	email "github.com/initialcapacity/email-address-validator"
)

func main() {
	err := email.Validate("grace.hopper@example.com")
	fmt.Println(err)
}
Output:
<nil>
Example (Invalid)
package main

import (
	"errors"
	"fmt"

	email "github.com/initialcapacity/email-address-validator"
)

func main() {
	err := email.Validate("grace..hopper@example.com")
	fmt.Println(err)
	fmt.Println(errors.Is(err, email.ErrInvalidLocalPart))
}
Output:
invalid local part: local part may not contain consecutive dots
true

func ValidateSMTPUTF8

func ValidateSMTPUTF8(addr string) error

ValidateSMTPUTF8 is like Validate but additionally accepts internationalized addresses per RFC 6531/6532 (SMTPUTF8): well-formed non-ASCII UTF-8 is permitted in the local part, and non-ASCII domain labels must be valid IDNA2008 U-labels.

Callers accepting user-entered domains should apply any desired IDNA mapping and normalization before constructing addr. ValidateSMTPUTF8 validates the supplied address but does not rewrite it.

Example
package main

import (
	"fmt"

	email "github.com/initialcapacity/email-address-validator"
)

func main() {
	addr := "用户@例子.example"
	fmt.Println(email.Validate(addr) == nil)
	fmt.Println(email.ValidateSMTPUTF8(addr) == nil)
}
Output:
false
true

Types

type SyntaxError

type SyntaxError struct {
	Addr   string // the address passed to Validate or ValidateSMTPUTF8
	Offset int    // byte offset in Addr at which the fault was detected
	Err    error  // sentinel classification: ErrInvalidLocalPart, ErrInvalidDomain, ...
	Detail string // description of the fault; may be empty
}

A SyntaxError describes why an email address failed validation and where the fault was detected. Every non-nil error returned by Validate and ValidateSMTPUTF8 is a *SyntaxError.

Err is one of this package's sentinel errors and is returned by Unwrap, so errors.Is(err, ErrInvalidDomain) and similar classifications work on a *SyntaxError.

Offset is a byte offset into Addr: the offending byte where one is known, otherwise the start of the offending component. For an invalid internationalized (IDNA) domain label, Offset is the start of that label.

Example
package main

import (
	"errors"
	"fmt"

	email "github.com/initialcapacity/email-address-validator"
)

func main() {
	err := email.Validate("ada@exa_mple.com")

	var syntaxErr *email.SyntaxError
	if errors.As(err, &syntaxErr) {
		fmt.Println(syntaxErr.Offset)
		fmt.Println(errors.Is(err, email.ErrInvalidDomain))
	}
}
Output:
7
true

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

func (*SyntaxError) Unwrap

func (e *SyntaxError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL