Grouped by task rather than alphabetically. For full
signatures and doc comments, see
pkg.go.dev.
The project is three modules. Unqualified names below live in the core
module github.com/bakhod1r/emailx, which has no
dependencies and never opens a network connection. Names written
dnsx.… live in github.com/bakhod1r/emailx/dns
and smtpx.… in
github.com/bakhod1r/emailx/smtp; those two are where all
networking lives.
Every function that touches the network has a …Context
variant taking a context.Context as its first argument. Only
the plain form is listed below; assume the context form exists.
Parsing
Function
Returns
Parse(input)
*Email, error — plain or header-style
ParseAddress(input)
*Email, error — "Name <a@b.com>"
ParseMany(inputs)
[]*Email, skipping failures
Normalize(input)
string, error — parse and normalize
NormalizeMany(inputs)
[]string
Unique(inputs)
[]string, deduplicated by normalized form
Inspecting an address
Method
Returns
e.Address()
the full address, normalized if Normalize ran
e.String()
same as Address, falling back to the raw input
e.LocalPart()
everything before @
e.BaseLocalPart()
local part without the plus tag
e.PlusTag() / e.HasPlusTag()
the sub-address tag
e.HasDot()
the local part contains a dot
e.Domain()
the domain, always lower-case
e.DomainName()
domain without the TLD
e.TLD() / e.Subdomain()
domain parts
e.DomainASCII() / e.DomainUnicode()
IDN conversion
e.Normalize()
rewrites in place, provider-aware
e.Equal(other)
normalized comparison
e.EqualNormalized(other)
same mailbox
e.EqualExact(other)
byte-for-byte
Classification
Call
Returns
e.IsDisposable()
domain is in the throwaway blocklist
IsDisposableDomain(domain)
the same check without parsing
DisposableDomainCount()
size of the bundled list
DisposableDomains()
[]string — the whole list, as a fresh copy
AllDisposableDomains()
iter.Seq[string] — the same, without copying
e.DisposableProvider()
name derived from the domain
e.IsRole()
local part is a function, not a person
e.IsRoleWithPrefixes(…)
against your own prefix list
e.Provider() / e.ProviderID()
*Provider{ID, Name, Domains, Free}
e.IsFree()
a known free consumer provider
Providers()
[]*Provider — one per provider, sorted by ID, Domains filled
AllProviders()
iter.Seq[*Provider] — the same, without copying
ProviderByID(id) / ProviderForDomain(domain)
*Provider, or nil
ProviderDomains()
every mapped domain, sorted
ProviderCount() / ProviderDomainCount()
table sizes
e.Country()
country name for the ccTLD
e.Suggestion()
corrected address for a typo domain, else ""
e.DomainSimilarity(target)
float64, 0 to 1
Validation
Call
Returns
IsValid(input)
bool, parse and check in one step
e.IsPossible()
cheap structural check
e.IsValid()
syntax check — ValidateSyntax without the reason
e.ValidateSyntax()
error naming the problem
IsValidSMTPUTF8(input) / e.IsValidSMTPUTF8()
the same, with RFC 6531 internationalized addresses allowed
e.ValidateSyntaxSMTPUTF8()
error, the SMTPUTF8 form
e.ValidateDomain()
error if the domain has no TLD
e.Validate()
ValidationResult — every offline check; the DNS fields stay false
ValidateMany(inputs, opts…)
map[string]ValidationResult, offline
dnsx.Validate(e)
the same result with MXValid, SPFValid, DMARCValid filled in
dnsx.ValidateMany(inputs, concurrency)
the batch form
dnsx.Analyze(input)
*Profile, error — everything, one round of lookups
What counts as valid. The accepted grammar is RFC 5322
dot-atom on both sides of the @: atext characters in
dot-separated atoms, and a domain of letter-digit-hyphen labels, within the
RFC 5321 limits of 64 octets for the local part and 253 for the domain.
IsValid, ValidateSyntax and Validate
all run the same check and cannot disagree.
Quoted local parts ("a b"@example.com) and address literals
(user@[192.0.2.1]) are valid per RFC 5321 but are
not accepted, because most receiving systems reject them too.
Inside dot-atom the rules are strict, and in places stricter than
net/mail: a label may not start or end with a hyphen, and a
local part may not begin with, end with, or contain consecutive dots.
The plain form is ASCII-only; the …SMTPUTF8 variants allow
a non-ASCII local part and a U-label domain.
DNS records
Call
Returns
dnsx.LookupInfo(domain, selectors…)
dnsx.Info — MX, SPF, DMARC, DKIM, concurrently
dnsx.LookupSPF(domain)
*SPF, following includes and redirects
dnsx.LookupDMARC(domain)
*DMARC
dnsx.LookupDKIM(domain, selectors…)
*DKIM, probing common selectors
dnsx.LookupDKIMSelector(domain, selector)
*DKIM for exactly one selector
dnsx.LookupMX(domain)
[]MXRecord, error
dnsx.HasMX, HasSPF, HasDMARC, HasDKIM
bool — presence only
dnsx.HasBIMI(domain)
*BIMIInfo{Enabled, LogoURL, VMC}
dnsx.HasMTASTS(domain)
*MTASTSInfo{Enabled, Raw}
dnsx.HasTLSRPT(domain)
*TLSRPTInfo{Enabled, RUAs}
dnsx.HasDNSSEC(domain)
bool — DNSKEY records exist
dnsx.CheckDomainHealth(domain, selectors…)
*DomainHealth — all of the above at once
These all live in github.com/bakhod1r/emailx/dns, imported
as dnsx throughout this page. To apply them to a parsed
address, pass e.Domain(). Every call has a
…Context variant.
Email also implements MarshalJSON,
UnmarshalJSON, MarshalText,
UnmarshalText, Scan and Value, so it
works directly in JSON structs and database columns.
Errors
ErrEmpty, ErrInvalidSyntax,
ErrInvalidLocalPart, ErrInvalidDomain,
ErrInvalidTLD, ErrLocalPartTooLong,
ErrDomainTooLong. ParseError carries an
ErrorCode for programmatic handling.