Library / SDK
AfterShip/email-verifier avatar
AfterShip/email-verifier

email-verifier checks mailboxes over SMTP, and port 25 is the hard part

:white_check_mark: A Go library for email verification without sending any emails.

1,647 stars238 forksGoMIT

At a glance

What is it?
AfterShip/email-verifier is a Go library that grades an email address along several axes, from string syntax to a live SMTP conversation. The parts that work anywhere are free; the part that actually proves a mailbox exists is off by default and depends on outbound port 25 and a sender domain you control.
Who is it for?
Adopt email-verifier when you need layered address hygiene in Go, since disposable, role and free provider tables ship in the repository, while MX checks use DNS. Do not adopt it as proof that a mailbox exists: that path needs EnableSMTPCheck, a sender domain with a PTR record, and outbound port 25, none of which most cloud hosts grant.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Five checks of different cost, stacked in one Verify call

The library answers one question in layers, and the layers are not equal in expense. NewVerifier returns a verifier you can use immediately, and Verify on it runs everything that costs nothing: syntax parsing, MX record lookup, and a set of metadata lookups against tables committed to the repository.

Those metadata files are the quiet strength. metadata_free.go holds free provider domains, metadata_role.go holds role account names such as support or billing, and metadata_disposable.go plus metadata_disposable_allowlist.go hold throwaway provider domains with an escape hatch for the false positives. address.go and suggestion.go cover parsing and typo suggestions, and gravatar.go is a separate profile lookup. None of that touches the network beyond DNS.

Verify returns a struct where each dimension is reported separately: Syntax carries the parsed username and domain plus a valid flag, HasMXRecords is a boolean, and Disposable, RoleAccount and Free are booleans of their own. Reachability is a three way field, and the README's own sample output shows it as "unknown" on a default verifier, which tells you the default path never attempts delivery level confirmation. Smtp and Gravatar come back null for the same reason.

Separating the axes is the right call for a signup form, where you may want to block disposable domains but still accept a role account.

Adding the package and building it

Installation is a single go get against the module path:

```shell script go get -u github.com/AfterShip/email-verifier

code

The import is aliased in the README examples because the package name and the module path differ:

go import ( "fmt" emailverifier "github.com/AfterShip/email-verifier" )

var ( verifier = emailverifier.NewVerifier() )

func main() { email := "[email protected]" ret, err := verifier.Verify(email) if err != nil { fmt.Println("verify email address failed, error is: ", err) return } if !ret.Syntax.Valid { fmt.Println("email address syntax is invalid") return } fmt.Println("email validation result", ret) } ```

What you should see is a populated result with reachability still unknown and smtp null. Running that same program with an address like someone at a domain with no MX record should show HasMXRecords false, which is the cheapest real signal in the library.

Go version support is tighter than the module path suggests. The README states that CI covers the three most recent Go releases, currently 1.25, 1.26 and 1.27, and that go.mod requires the oldest of them, so an older toolchain will refuse to build the module. go.mod declares go 1.25.0 and pulls in golang.org/x/net v0.58.0, github.com/hbollon/go-edlib, julienschmidt/httprouter and stretchr/testify.

CheckSMTP is the check that actually talks to a mail server

SMTP verification is opt in through a builder chain, and the README is blunt about why:

go
var (
    verifier = emailverifier.
        NewVerifier().
        EnableSMTPCheck()
)

func main() {
    domain := "domain.org"
    username := "username"
    ret, err := verifier.CheckSMTP(domain, username)
    if err != nil {
        fmt.Println("check smtp failed: ", err)
        return
    }
    fmt.Println("smtp validation result: ", ret)
}

Because most ISPs block outgoing SMTP on port 25 to prevent spam, the check is disabled by default and only works where port 25 is usable from your host. That single fact decides whether the feature is available to you at all, and it is a network policy question long before it is a code question.

Two switches shape the exchange. EnableSMTPCheck turns the conversation on. DisableCatchAllCheck turns off the probe of a catch all domain, and the README notes it takes effect only when SMTP verification is enabled, so setting it on a default verifier does nothing. Catch all detection being on by default is deliberate: many corporate domains accept any recipient, so an accepted RCPT alone proves very little.

The other detail worth internalising is sender identity. The defaults for FromEmail and HelloName are [email protected] and localhost, and the README points out that example.org is reserved by RFC 2606, so a server that validates senders can reject the whole exchange before it looks at your target address. Such rejections land at MAIL FROM and name the sender or the connecting host, with 550 5.7.25 forward confirmed reverse DNS failed and 550 5.7.1 Spamhaus host blocks given as examples. Set both fields to a domain you control, with a PTR record for the IP you connect from, before verifying at any volume.

The SOCKS5 proxy covers the connection but not DNS

For networks that block port 25 but allow proxying, Proxy takes a URI:

go
var (
    verifier = emailverifier.
        NewVerifier().
        EnableSMTPCheck().
    	Proxy("socks5://user:[email protected]:1080")
)

The scheme must be socks5 or socks5h, the only two that golang.org/x/net/proxy resolves. Anything else, socks4 and socks4a included, fails the dial with proxy: unknown scheme, and the README's fix is to register your own dialer type with proxy.RegisterDialerType before calling Proxy.

Two limitations follow from how that dependency works, and both are easy to be surprised by. A query string in the URI is ignored, since only scheme, credentials and host are read, so a ?timeout=5s does nothing and the timeouts belong in ConnectTimeout and OperationTimeout instead. And DNS does not go through the proxy: only the connection to the mail server is proxied, so MX lookups still leave from the local machine.

The Resolver option is how you take DNS back under control. The default is net.DefaultResolver, and supplying your own *net.Resolver overrides MX lookups and, when no proxy is set, direct SMTP host lookups too. When a proxy is configured, SMTP hostname resolution follows the proxy path instead, while MX lookups always use your resolver. Routing DNS through the SOCKS5 proxy means dialing your DNS server over TCP, which costs more than plain UDP.

Where the library's confidence actually comes from

Two things set this apart from a regular expression plus an MX check. The first is that the disposable and free provider lists are versioned data files in the repository rather than a remote API call, so classification works offline and cannot silently change under you. The disposable allowlist exists precisely because that data goes stale, and maintaining it becomes your job on upgrade.

The second is that the library does not pretend its SMTP verdict is absolute. Reachability is a confidence field rather than a boolean, and error.go and handler.go separate failure to determine from a negative determination. For a product decision that matters, a silent network error should never be read as a nonexistent mailbox, and separating the two is what lets you build a retry queue instead of dropping signups.

The repository also carries smtp_by_api.go, an alternative SMTP path that goes through an HTTP API instead of a direct conversation, plus schedule.go for timing related logic and cmd/ for a command entry point.

Wrong tool for a hard compliance problem, right tool for a signup form

This library will not satisfy a requirement to prove identity. An SMTP RCPT TO acceptance tells you the domain's mail server did not reject the address at that moment; it is not evidence a human reads that inbox, and catch all domains accept everything. If you need verified ownership, you send a confirmation message with a token, which is a different mechanism entirely.

The alternative approaches split cleanly. A hosted verification API returns judgement from someone else's infrastructure, which sidesteps port 25 and the PTR problem but sends your address list to a third party and adds a per check cost. A hand rolled checker built on net/smtp gives you full control and no dependency, but you inherit the catch all ambiguity and the sender reputation problem yourself. Choosing email-verifier over the second option buys you the metadata tables and the layered result struct; choosing it over the first is a decision to keep address data in your own process.

The MIT licence permits embedding the library in a commercial signup flow while retaining its copyright and licence notices.

Upgrades mean reviewing your metadata tables

The last push was on 2026-10-13 and v1.5.0 shipped on 2026-09-10, after a long quiet stretch following v1.4.1 in September 2024. That gap is the upgrade cost to plan for: a release that lands after a year of little activity has more chance of carrying breaking behaviour changes than a routine one.

Development tooling is conventional and worth knowing about. The Makefile exposes test running go test with the race detector and an atomic cover profile, a detect_race target for verbose race runs, and a lint target that runs go vet followed by golangci-lint, which is configured through .golangci.yml with pre-commit hooks in .pre-commit-config.yaml. CI runs through GitHub Actions. Running make test locally before bumping the version is the cheap check, and the coverage badge in the README is wired to coveralls.

Because the free, disposable and role lists are source files, an upgrade is not only an API review. Diff metadata_disposable.go and metadata_free.go between the two versions, because your blocking behaviour can change without any call signature changing.

Editorial conclusion

Adopt email-verifier when you need layered address hygiene in Go, since disposable, role and free provider tables ship in the repository, while MX checks use DNS. Do not adopt it as proof that a mailbox exists: that path needs EnableSMTPCheck, a sender domain with a PTR record, and outbound port 25, none of which most cloud hosts grant. Before you rely on it, set FromEmail and HelloName to a domain you control and run the Makefile test target to see how the SMTP path behaves from your network.

Frequently asked questions

Why is SMTP verification disabled by default in email-verifier?

Most ISPs block outgoing SMTP requests on port 25 to prevent spam, so a default verifier would fail on nearly every host. Call EnableSMTPCheck on the verifier to turn the check on where port 25 is usable.

How do I install email-verifier in a Go project?

The README gives a single command: go get -u github.com/AfterShip/email-verifier. Go 1.25 or newer is required, since go.mod declares go 1.25.0 and an older toolchain refuses to build the module.

Can I verify emails through a SOCKS5 proxy?

Yes, with limits. The scheme must be socks5 or socks5h, a query string in the URI is ignored so timeouts must use ConnectTimeout and OperationTimeout, and DNS lookups still go out from the local machine unless you also supply a Resolver.

Does email-verifier detect disposable and free provider addresses?

Yes, from data files committed to the repository rather than a remote lookup, with a disposable allowlist for providers that should not be blocked. Reachability, by contrast, stays unknown unless SMTP checking is enabled.

Official sources

  1. AfterShip/email-verifier on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aftership-email-verifier.svg)](https://hysenlabs.com/projects/aftership-email-verifier)

Was this useful?