emailx

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 14 Imported by: 0

README

emailx

Email address parsing, validation, and mail-domain intelligence for Go.

Website · API docs · Changelog

emailx parses an address, normalizes it, and answers the questions that actually matter before you send mail to it or accept it at signup: is the domain real, is it a throwaway, is it a role account, and is the domain's anti-spoofing setup real or cosmetic.

Three modules

The offline half and the network half are separate Go modules, so you only take on the dependencies of the part you actually use:

Module Import Dependencies Touches the network
core github.com/bakhod1r/emailx none — standard library only never
DNS github.com/bakhod1r/emailx/dns github.com/miekg/dns yes
SMTP github.com/bakhod1r/emailx/smtp via the DNS module yes
import (
    "github.com/bakhod1r/emailx"            // parse, normalize, classify
    dnsx "github.com/bakhod1r/emailx/dns"   // MX, SPF, DMARC, DKIM, ...
    smtpx "github.com/bakhod1r/emailx/smtp" // mailbox probing
)

If all you need is parsing, normalization, provider lookup, and the disposable-domain list, go get github.com/bakhod1r/emailx adds a single module to your build and nothing else. This is enforced in CI: the core module's requirement list must stay empty and the core package must not import a networking package.

Quick start

e, err := emailx.Parse("John.Doe+news@Gmail.com")
if err != nil {
    log.Fatal(err)
}

e.Address()       // "John.Doe+news@gmail.com"
e.LocalPart()     // "John.Doe+news"
e.BaseLocalPart() // "John.Doe"
e.PlusTag()       // "news"
e.Provider().Name // "Gmail"
e.IsFree()        // true
e.IsDisposable()  // false

e.Normalize()
e.Address() // "johndoe@gmail.com"

What it does

Parsing and normalization
Method Result
Parse / ParseMany parse one or many addresses
Normalize / NormalizeMany provider-aware canonical form (Gmail dots, +tags)
Unique deduplicate a list by normalized form
PlusTag, BaseLocalPart sub-addressing
DomainName, TLD, Subdomain domain parts
DomainASCII, DomainUnicode IDN conversion
Equal, EqualNormalized, EqualExact comparison
Validation
emailx.IsValid("user@example.com")   // true
e.ValidateSyntax()                    // the same check, with the reason
e.Validate()                          // every offline check at once

IsValid is exactly ValidateSyntax without the reason, so the two never disagree about an address.

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.

Two forms are valid per RFC 5321 but deliberately not accepted, because most receiving systems will not accept them either:

Not supported Example
quoted local part "a b"@example.com, "with@at"@example.com
address literal user@[192.0.2.1], user@[IPv6:2001:db8::1]

Inside dot-atom the rules are enforced strictly, and in places more strictly 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. For RFC 6531 internationalized addresses — a non-ASCII local part, a U-label domain — use the SMTPUTF8 variants:

emailx.IsValidSMTPUTF8("денис@münchen.de") // true
e.ValidateSyntaxSMTPUTF8()                  // with the reason

Delivery to those needs a server advertising the SMTPUTF8 extension, which is why it is a separate call rather than the default.

Classification

IsDisposable checks against the full disposable-email-domains blocklist (8000+ domains) bundled at build time. IsRole covers 59 role prefixes. Provider identifies 20 mail providers, Country maps 248 country-code TLDs.

Enumerating the bundled data

The provider table and the disposable-domain list can be walked, not just queried — which is what a generator of synthetic or test data needs:

for p := range emailx.AllProviders() {
    p.ID      // "gmail"
    p.Name    // "Gmail"
    p.Domains // ["gmail.com", "googlemail.com"]
    p.Free    // true
}

for d := range emailx.AllDisposableDomains() {
    // 8000+ domains, no copy made
}
Function Result
Providers() []*Provider, one per provider, sorted by ID, domains filled
AllProviders() iter.Seq[*Provider], no allocation
ProviderByID, ProviderForDomain single lookup
ProviderDomains(), ProviderCount(), ProviderDomainCount() the domain list and its size
DisposableDomains() []string, a fresh copy of the whole list
AllDisposableDomains() iter.Seq[string], no allocation
DisposableDomainCount() list size

The slice-returning forms hand back copies, so a caller cannot corrupt the package tables; the iterator forms avoid copying several thousand entries.

DNS and mail-domain intelligence
info := dnsx.LookupInfo("example.com")

info.HasMX()            // MX records exist
info.SPF.IsStrict()     // the record ends in -all
info.SPF.Lookups        // DNS lookups the record costs
info.SPF.TooManyLookups // over the RFC 7208 limit of 10
info.DMARC.IsEnforcing()// p=reject or p=quarantine at pct=100
info.DKIM.Found         // a key was found by probing common selectors
info.IsProtected()      // all three, properly configured

The four record types are fetched concurrently, so the call costs about one round trip.

Beyond that: dnsx.BIMI, dnsx.MTASTS, dnsx.TLSRPT, dnsx.HasDNSSEC, dnsx.CheckDomainHealth, and dnsx.Analyze for everything in one call.

dnsx.CheckRisk and dnsx.CheckDeliverability are built on these parsers rather than on plain "record exists" booleans, so -all scores above ~all, p=reject above p=none, and an SPF record over the lookup limit is scored no better than having none. Both carry an explanation of every deduction (Risk.Signals, Deliverability.Reasons).

Why the distinctions matter:

  • An SPF record over the 10-lookup budget is a permerror. Receivers stop evaluating it, so the policy silently stops protecting the domain.
  • p=none is a DMARC record that reports but blocks nothing. So is p=reject; pct=20, four times out of five.
  • A DKIM record with an empty p= is a revoked key, and t=y means the key is in testing mode and failures must be ignored.

IsProtected() accounts for all of these; a plain "has SPF/DKIM/DMARC" boolean does not.

SMTP verification
res := smtpx.Verify("user@example.com")
res.Status // valid | invalid | catch-all | unknown

The probe connects to the domain's MX in priority order, runs MAIL FROM and RCPT TO, then repeats with a random address to detect a catch-all server. A catch-all result means acceptance proves nothing about that particular mailbox.

Before relying on this in production:

  • Port 25 outbound is blocked by most cloud providers. You will get unknown everywhere unless the network allows it.
  • Unthrottled probing gets your IP blocklisted. Use smtpx.Options.Limiter.
  • Many large providers answer every probe identically by design.
  • 4xx is reported as unknown with Greylisted set, never as invalid. Retry later before drawing a conclusion.
Privacy

Mask, HashSHA256, HashSHA512, and Fingerprint(WithSecret(...)) for storing or comparing addresses without keeping the plaintext.

Configuration

Every network call has a context variant (dnsx.LookupMXContext, dnsx.LookupSPFContext, smtpx.VerifyContext, …). The non-context forms apply a package-wide deadline:

dnsx.SetDefaultTimeout(3 * time.Second)
Custom DNS server
dnsx.SetResolver(dnsx.NewResolver("1.1.1.1:53", 2*time.Second))

NewResolver goes through the system resolver machinery, which hides record TTLs. NewDNSResolver queries the server directly and exposes them:

dnsx.SetResolver(dnsx.NewDNSResolver("1.1.1.1:53", 2*time.Second))

It also rejoins TXT records that arrive split into 255-byte chunks, which long SPF and DKIM records require in order to parse.

Caching
cache := dnsx.EnableCache(5 * time.Minute)
cache.SetMaxSize(50000)

The cache:

  • stores failures, so a batch of addresses on a dead domain costs one lookup rather than one per address;
  • collapses concurrent lookups of the same name into a single upstream query, so a burst of requests for one domain does not become a burst of DNS traffic;
  • honours record TTLs when the underlying resolver reports them (that is, with NewDNSResolver), clamped to [MinCacheTTL, ttl]. Otherwise every entry lives for the fixed window;
  • is bounded at DefaultCacheSize (10000) entries.
Rate limiting SMTP probes
limiter := smtpx.NewRateLimiter(5, 10) // 5/s, bursts of 10

res := smtpx.Verify(addr, smtpx.Options{Limiter: limiter})

Share one limiter across all probes. Any type with Wait(ctx) error satisfies smtpx.Limiter, so golang.org/x/time/rate drops in directly.

SetResolver and SetDefaultTimeout are safe to call concurrently with in-flight lookups.

Command line

go install github.com/bakhod1r/emailx/cmd/emailx@latest
emailx analyze  <email>     full intelligence profile
emailx validate <email>     syntax, domain, and DNS validation
emailx domain   <domain>    SPF, DMARC, DKIM, MX, transport security
emailx verify   <email>     SMTP mailbox probe
emailx batch    <file|->    a list of addresses, one per line or CSV

Every command takes -json, -timeout, -dns host:port, and -cache.

$ emailx domain google.com
Domain: google.com

MX:        yes (smtp.google.com.)
SPF:       yes (~all, 1 lookup)
DMARC:     yes (p=reject, pct=100, enforcing)
DKIM:      no key found on the common selectors
Protected: no
DNSSEC: no  BIMI: no  MTA-STS: yes  TLS-RPT: yes

batch reads a plain list or a CSV column (-column, -header), runs -concurrency addresses at once, and writes CSV or JSON:

$ emailx batch addresses.csv -column 1 -header
address,valid,mx,protected,disposable,role,risk,deliverability,error
ada@gmail.com,true,true,false,false,false,LOW,80,
bad-address,false,false,false,false,false,,0,emailx: invalid syntax

Batch use

Offline only — no network, so no rate to limit:

results := emailx.ValidateMany(addresses, emailx.WithConcurrency(20))

With DNS checks (MXValid, SPFValid, DMARCValid) filled in as well:

dnsx.EnableCache(5 * time.Minute) // repeated domains then cost one lookup
results := dnsx.ValidateMany(addresses, 20)

Regenerating the bundled data

generated.go is built from upstream sources and checked in:

go generate ./...          # fetches the current lists
go run ./internal/generator -offline   # rebuild without network access

The generator refuses to shrink the tables, and never lets a known provider domain be written into the disposable list.

Status

Current release: v0.4.0, which made the syntax entry points agree with each other and added the SMTPUTF8 variants. v0.3.0 split the network code into the dns and smtp modules. The API is pre-1.0 and may change; see CHANGELOG.md for the migration table.

DNS, SPF, DMARC, DKIM, and SMTP paths are covered by tests that stub the network — the suite makes no live DNS or SMTP calls and runs in under a second. Library coverage is 95%.

License

MIT. See LICENSE.

Documentation

Overview

Package emailx parses, normalizes, and classifies email addresses.

It answers the questions that can be answered from the address itself: is the syntax valid, is the domain a throwaway, is this a role account, which provider serves it, and do two addresses reach the same mailbox.

No network, no dependencies

This package never opens a network connection and imports nothing outside the standard library. That is a guarantee, not an accident: the code that does touch the network lives in two separate modules, github.com/bakhod1r/emailx/dns and github.com/bakhod1r/emailx/smtp, so a program that only needs the offline half links no third-party code and takes on no transitive dependencies.

import (
	"github.com/bakhod1r/emailx"           // offline, zero dependencies
	dnsx "github.com/bakhod1r/emailx/dns"  // MX, SPF, DMARC, DKIM, ...
	smtpx "github.com/bakhod1r/emailx/smtp" // mailbox probing
)

Parsing

Parse returns an *Email whose parts can be inspected and compared:

e, err := emailx.Parse("John.Doe+news@Gmail.com")
e.BaseLocalPart() // "John.Doe"
e.PlusTag()       // "news"
e.Normalize()
e.Address()       // "johndoe@gmail.com"

Normalization is provider-aware, so two addresses that reach the same mailbox compare equal with EqualNormalized.

What counts as valid

The accepted grammar is RFC 5322 dot-atom on both sides of the "@": atext characters grouped into dot-separated atoms, and a domain of letter-digit-hyphen labels, within the RFC 5321 length limits of 64 octets for the local part and 253 for the domain.

Two forms that are valid per RFC 5321 are deliberately not accepted, because most systems that receive an address will not accept them either:

  • quoted local parts — `"a b"@example.com`, `"with@at"@example.com`
  • address literals — user@[192.0.2.1], user@[IPv6:2001:db8::1]

Within dot-atom the rules are enforced strictly, and more strictly than net/mail in places: a label may not start or end with a hyphen, and the local part may not begin with, end with, or contain consecutive dots.

IsValid, ValidateSyntax, and Validate all run the same check, so they can never disagree about an address. IsValid is the boolean form of ValidateSyntax, which returns the reason.

The plain form is ASCII-only. IsValidSMTPUTF8 and ValidateSyntaxSMTPUTF8 apply the same rules with RFC 6531 internationalized addresses allowed — a non-ASCII local part and a U-label domain. Delivery to those needs a server advertising the SMTPUTF8 extension.

Classification

IsDisposableDomain tests a domain against the bundled throwaway-mail list. Provider reports which mail provider serves an address, if it is a known one. Validate runs every offline check at once.

Enumerating the bundled data

The provider table and the disposable-domain list can be walked, not only queried, which is what a generator of test or synthetic data needs:

for p := range emailx.AllProviders() {
	fmt.Println(p.ID, p.Domains) // "gmail", ["gmail.com" "googlemail.com"]
}

for d := range emailx.AllDisposableDomains() {
	fmt.Println(d)
}

Providers and DisposableDomains return the same data as fresh slices when a copy is wanted; the iterator forms avoid copying several thousand entries.

Concurrency

All exported functions are safe for concurrent use.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrEmpty            = errors.New("emailx: empty input")
	ErrInvalidSyntax    = errors.New("emailx: invalid syntax")
	ErrInvalidLocalPart = errors.New("emailx: invalid local part")
	ErrInvalidDomain    = errors.New("emailx: invalid domain")
	ErrInvalidTLD       = errors.New("emailx: invalid top-level domain")
	ErrInvalidUnicode   = errors.New("emailx: invalid unicode")
	ErrDomainTooLong    = errors.New("emailx: domain too long")
	ErrLocalPartTooLong = errors.New("emailx: local part too long")
)
View Source
var ErrInvalidPunycode = errors.New("emailx: invalid punycode label")

ErrInvalidPunycode reports a label that is not decodable Punycode.

Functions

func AllDisposableDomains added in v0.3.0

func AllDisposableDomains() iter.Seq[string]

AllDisposableDomains iterates every bundled throwaway-mail domain without allocating a copy. Returning false from the loop body stops the walk.

for d := range emailx.AllDisposableDomains() {
	fmt.Println(d)
}
Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	// The full list is a few thousand entries; iterating avoids copying it.
	n := 0
	for range emailx.AllDisposableDomains() {
		n++
	}
	fmt.Println(n == emailx.DisposableDomainCount())
}
Output:
true

func AllProviders added in v0.3.0

func AllProviders() iter.Seq[*Provider]

AllProviders iterates every known provider in ID order without allocating a slice. The values it yields are the package's own entries and must not be modified; use Providers when a mutable copy is wanted.

for p := range emailx.AllProviders() {
	fmt.Println(p.ID, len(p.Domains))
}

func DisposableDomainCount

func DisposableDomainCount() int

DisposableDomainCount reports how many domains are in the bundled list, which is useful for confirming the data was regenerated.

func DisposableDomains added in v0.3.0

func DisposableDomains() []string

DisposableDomains returns every bundled throwaway-mail domain, in the sorted order of the upstream list. The result is a fresh copy of a multi-thousand-entry slice; prefer AllDisposableDomains when the list is only being read.

func IsDisposableDomain

func IsDisposableDomain(domain string) bool

IsDisposableDomain reports whether a domain belongs to a known throwaway-mail provider.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	fmt.Println(emailx.IsDisposableDomain("mailinator.com"))
	fmt.Println(emailx.IsDisposableDomain("gmail.com"))
}
Output:
true
false

func IsValid

func IsValid(input string) bool

IsValid parses input and reports whether it is a syntactically valid address. It is the one-step form of Parse followed by (*Email).IsValid.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	fmt.Println(emailx.IsValid("user@example.com"))
	fmt.Println(emailx.IsValid("not-an-address"))
}
Output:
true
false

func IsValidSMTPUTF8 added in v0.4.0

func IsValidSMTPUTF8(input string) bool

IsValidSMTPUTF8 parses input and reports whether it is a syntactically valid RFC 6531 internationalized address.

func Normalize

func Normalize(input string) (string, error)

Normalize parses and normalizes the input email, returning the normalized string.

func NormalizeMany

func NormalizeMany(inputs []string) []string

NormalizeMany normalizes a slice of email strings.

func ProviderCount added in v0.3.0

func ProviderCount() int

ProviderCount reports how many distinct providers are known.

func ProviderDomainCount added in v0.3.0

func ProviderDomainCount() int

ProviderDomainCount reports how many domains the provider table maps.

func ProviderDomains added in v0.3.0

func ProviderDomains() []string

ProviderDomains returns every domain in the provider table, sorted. The result is a fresh copy.

func Unique

func Unique(inputs []string) []string

Unique returns a slice of unique normalized email addresses from the inputs.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	for _, addr := range emailx.Unique([]string{
		"john.doe@gmail.com",
		"JohnDoe@gmail.com",
		"other@example.com",
	}) {
		fmt.Println(addr)
	}
}
Output:
johndoe@gmail.com
other@example.com

func ValidateMany

func ValidateMany(inputs []string, opts ...BatchOptions) map[string]ValidationResult

ValidateMany validates multiple emails, optionally concurrently.

Types

type BatchOptions

type BatchOptions struct {
	Concurrency int
}

func WithConcurrency

func WithConcurrency(n int) BatchOptions

type Email

type Email struct {
	// contains filtered or unexported fields
}

Email represents a parsed email address.

func Parse

func Parse(input string) (*Email, error)

Parse parses an email string into an Email struct. It supports plain addresses and header-style addresses with angle brackets.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	e, err := emailx.Parse("John.Doe+news@Gmail.com")
	if err != nil {
		panic(err)
	}

	fmt.Println(e.LocalPart())
	fmt.Println(e.BaseLocalPart())
	fmt.Println(e.PlusTag())
	fmt.Println(e.Domain())
}
Output:
John.Doe+news
John.Doe
news
gmail.com

func ParseAddress

func ParseAddress(input string) (*Email, error)

ParseAddress parses a header-style address like "John Doe <john@example.com>"

func ParseMany

func ParseMany(inputs []string) []*Email

ParseMany parses a slice of email strings.

func (*Email) Address

func (e *Email) Address() string

Address returns the complete email address in its normalized form. It is an alias for String().

func (*Email) BaseLocalPart

func (e *Email) BaseLocalPart() string

BaseLocalPart returns the local part without the plus tag.

func (*Email) Country

func (e *Email) Country() string

Country returns the country name associated with the email's ccTLD, if any. E.g. for "user@mail.uz" it returns "Uzbekistan".

func (*Email) DisposableProvider

func (e *Email) DisposableProvider() string

DisposableProvider returns the provider name for a disposable address, or an empty string. The upstream list carries no brand names, so the name is derived from the domain: "mailinator.com" gives "mailinator".

func (*Email) Domain

func (e *Email) Domain() string

Domain returns the domain part of the email address (after the @).

func (*Email) DomainASCII

func (e *Email) DomainASCII() (string, error)

DomainASCII returns the punycode (A-label) representation of the domain. See punycode.go for what this does and does not validate.

func (*Email) DomainName

func (e *Email) DomainName() string

DomainName returns the domain name without the TLD. E.g., for "gmail.com" it returns "gmail".

func (*Email) DomainSimilarity

func (e *Email) DomainSimilarity(target string) float64

DomainSimilarity calculates the similarity between the email's domain and another domain. Uses a basic Levenshtein distance normalized to [0, 1].

func (*Email) DomainUnicode

func (e *Email) DomainUnicode() (string, error)

DomainUnicode returns the unicode (U-label) representation of the domain.

func (*Email) Equal

func (e *Email) Equal(other *Email) bool

Equal checks if two emails are considered equal. This is an alias for EqualNormalized.

func (*Email) EqualExact

func (e *Email) EqualExact(other *Email) bool

EqualExact checks if the raw input of the two emails is exactly the same.

func (*Email) EqualNormalized

func (e *Email) EqualNormalized(other *Email) bool

EqualNormalized checks if the normalized form of the two emails is the same.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	a, _ := emailx.Parse("john.doe@gmail.com")
	b, _ := emailx.Parse("JohnDoe+shopping@gmail.com")

	// Both addresses reach the same mailbox.
	fmt.Println(a.EqualNormalized(b))
}
Output:
true

func (*Email) Fingerprint

func (e *Email) Fingerprint(opts ...FingerprintOptions) string

Fingerprint returns a unique identifier for the email, useful for database deduplication.

func (*Email) HasDot

func (e *Email) HasDot() bool

HasDot returns true if the local part has a dot.

func (*Email) HasPlusTag

func (e *Email) HasPlusTag() bool

HasPlusTag returns true if the local part has a plus tag.

func (*Email) HashSHA256

func (e *Email) HashSHA256() string

HashSHA256 returns the SHA256 hash of the normalized email.

func (*Email) HashSHA512

func (e *Email) HashSHA512() string

HashSHA512 returns the SHA512 hash of the normalized email.

func (*Email) IsDisposable

func (e *Email) IsDisposable() bool

IsDisposable returns true if the email domain is a known disposable provider.

func (*Email) IsFree

func (e *Email) IsFree() bool

IsFree returns true if the email is hosted by a known free provider.

func (*Email) IsPossible

func (e *Email) IsPossible() bool

IsPossible does a fast check on the email.

func (*Email) IsRole

func (e *Email) IsRole() bool

IsRole returns true if the email local part represents a common role account.

func (*Email) IsRoleWithPrefixes

func (e *Email) IsRoleWithPrefixes(prefixes ...string) bool

IsRoleWithPrefixes checks against a custom list of prefixes.

func (*Email) IsValid

func (e *Email) IsValid() bool

IsValid reports whether the address is syntactically valid: RFC 5322 dot-atom on both sides, within the RFC 5321 length limits. It is exactly ValidateSyntax without the reason, so the two can never disagree.

The local part must be ASCII. Use IsValidSMTPUTF8 for RFC 6531 addresses.

func (*Email) IsValidSMTPUTF8 added in v0.4.0

func (e *Email) IsValidSMTPUTF8() bool

IsValidSMTPUTF8 is IsValid with RFC 6531 internationalized addresses allowed: the local part may contain non-ASCII characters and the domain may be a U-label. Sending to such an address needs a server advertising the SMTPUTF8 extension.

func (*Email) LocalPart

func (e *Email) LocalPart() string

LocalPart returns the local part of the email address (before the @).

func (Email) MarshalJSON

func (e Email) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface.

func (Email) MarshalText

func (e Email) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (*Email) Mask

func (e *Email) Mask(opts ...MaskOptions) string

Mask masks the email address for privacy.

func (*Email) Normalize

func (e *Email) Normalize()

Normalize normalizes the email address in-place.

The domain is lower-cased, and so is the local part: local parts are case-sensitive in the RFCs, but no mail provider in practice treats them that way, and comparing addresses case-sensitively causes duplicate signups.

Where the provider is known, provider-specific rules also apply: Gmail ignores dots, and most providers ignore a "+tag" suffix. Addresses that reach the same mailbox therefore normalize to the same string.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	e, _ := emailx.Parse("John.Doe+newsletter@Gmail.com")
	e.Normalize()
	fmt.Println(e.Address())
}
Output:
johndoe@gmail.com

func (*Email) PlusTag

func (e *Email) PlusTag() string

PlusTag returns the plus tag if present, otherwise empty.

func (*Email) Provider

func (e *Email) Provider() *Provider

Provider returns the provider metadata if matched, otherwise nil.

func (*Email) ProviderID

func (e *Email) ProviderID() string

ProviderID returns the ID of the provider, or empty string.

func (*Email) Scan

func (e *Email) Scan(value interface{}) error

Scan implements the sql.Scanner interface.

func (*Email) String

func (e *Email) String() string

String returns the normalized email address if available, otherwise the raw email.

func (*Email) Subdomain

func (e *Email) Subdomain() string

Subdomain returns the subdomain if present, otherwise empty. E.g., for "mail.yahoo.com" it returns "mail".

func (*Email) Suggestion

func (e *Email) Suggestion() string

Suggestion returns a suggested correction if the domain looks like a typo of a popular domain.

func (*Email) TLD

func (e *Email) TLD() string

TLD returns the top-level domain. E.g., for "gmail.com" it returns "com".

func (*Email) UnmarshalJSON

func (e *Email) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface.

func (*Email) UnmarshalText

func (e *Email) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

func (*Email) Validate

func (e *Email) Validate() ValidationResult

Validate performs every check this package can make without a network call: syntax, domain shape, disposability, and role accounts.

func (*Email) ValidateDomain

func (e *Email) ValidateDomain() error

ValidateDomain checks basic domain correctness.

func (*Email) ValidateSyntax

func (e *Email) ValidateSyntax() error

ValidateSyntax validates the syntax strictly, returning the reason it failed. IsValid is the boolean form of the same check.

func (*Email) ValidateSyntaxSMTPUTF8 added in v0.4.0

func (e *Email) ValidateSyntaxSMTPUTF8() error

ValidateSyntaxSMTPUTF8 is ValidateSyntax with RFC 6531 addresses allowed.

func (Email) Value

func (e Email) Value() (driver.Value, error)

Value implements the driver.Valuer interface.

type EmailIndex

type EmailIndex struct {
	// contains filtered or unexported fields
}

EmailIndex provides fast lookups for millions of emails.

func NewIndex

func NewIndex() *EmailIndex

NewIndex creates a new EmailIndex.

func (*EmailIndex) Add

func (idx *EmailIndex) Add(e *Email)

Add adds an email to the index.

func (*EmailIndex) Exists

func (idx *EmailIndex) Exists(input string) bool

Exists checks if a normalized email exists in the index.

func (*EmailIndex) FindByDomain

func (idx *EmailIndex) FindByDomain(domain string) []*Email

FindByDomain returns emails matching the domain.

func (*EmailIndex) FindByProvider

func (idx *EmailIndex) FindByProvider(providerID string) []*Email

FindByProvider returns emails matching the provider ID.

type EmailSet

type EmailSet struct {
	// contains filtered or unexported fields
}

EmailSet is a thread-safe set of emails.

func NewSet

func NewSet() *EmailSet

NewSet creates a new EmailSet.

func (*EmailSet) Add

func (s *EmailSet) Add(input string) error

Add parses and adds an email to the set.

func (*EmailSet) ByDomain

func (s *EmailSet) ByDomain(domain string) []*Email

ByDomain returns a slice of emails matching the given domain.

func (*EmailSet) Len

func (s *EmailSet) Len() int

Len returns the number of unique emails in the set.

type ErrorCode

type ErrorCode string
const (
	ErrorCodeEmpty            ErrorCode = "empty"
	ErrorCodeInvalidSyntax    ErrorCode = "invalid_syntax"
	ErrorCodeInvalidLocalPart ErrorCode = "invalid_local_part"
	ErrorCodeInvalidDomain    ErrorCode = "invalid_domain"
	ErrorCodeInvalidTLD       ErrorCode = "invalid_tld"
	ErrorCodeInvalidUnicode   ErrorCode = "invalid_unicode"
	ErrorCodeDomainTooLong    ErrorCode = "domain_too_long"
	ErrorCodeLocalPartTooLong ErrorCode = "local_part_too_long"
)

type FingerprintOptions

type FingerprintOptions struct {
	Secret string
}

func WithSecret

func WithSecret(secret string) FingerprintOptions

type MaskOptions

type MaskOptions struct {
	LocalPrefix int
	LocalSuffix int
	MaskChar    rune
}

type ParseError

type ParseError struct {
	Code     ErrorCode
	Position int
	Input    string
}

func (*ParseError) Error

func (e *ParseError) Error() string

type Provider

type Provider struct {
	ID      string
	Name    string
	Domains []string
	Free    bool
}

Provider represents information about an email provider.

func ProviderByID added in v0.3.0

func ProviderByID(id string) *Provider

ProviderByID returns the provider with the given ID, or nil. The result is a fresh copy.

func ProviderForDomain added in v0.3.0

func ProviderForDomain(domain string) *Provider

ProviderForDomain returns the provider serving a domain, or nil. It is the package-level form of (*Email).Provider. The result is a fresh copy.

func Providers added in v0.3.0

func Providers() []*Provider

Providers returns every known email provider, one entry per provider, sorted by ID. Each entry carries the full sorted list of domains that provider serves, which is what a generator needs to pick a realistic address. The result is a fresh copy; mutating it does not affect the package.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/emailx"
)

func main() {
	// Enumerating the table is what a generator needs: pick a provider, then
	// pick one of its domains.
	for _, p := range emailx.Providers() {
		if p.ID == "gmail" {
			fmt.Println(p.Name, p.Domains)
		}
	}
}
Output:
Gmail [gmail.com googlemail.com]

type ValidationResult

type ValidationResult struct {
	Valid       bool
	SyntaxValid bool
	DomainValid bool
	MXValid     bool
	SPFValid    bool
	DMARCValid  bool
	Disposable  bool
	Role        bool
}

ValidationResult holds the result of extensive validation.

Validate fills only the offline fields. MXValid, SPFValid and DMARCValid require DNS and are left false; the emailx/dns subpackage's Validate fills them in.

Directories

Path Synopsis
cmd
emailx module
dns module
internal
generator command
Command generator rebuilds generated.go from upstream data sources.
Command generator rebuilds generated.go from upstream data sources.
smtp module

Jump to

Keyboard shortcuts

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