Self-hosted service
umuterturk/email-verifier avatar
umuterturk/email-verifier

email-verifier validates addresses without ever opening an SMTP session

Privacy-first, Free, Open Source email verifier

609 stars98 forksGoMIT

At a glance

What is it?
email-verifier is a Go service that checks an address for syntax, MX records, disposable domains and role accounts, and returns a status, a score and a set of validation flags. Its most interesting decision is a negative one: it never performs an SMTP RCPT TO probe, because the project argues such a probe would produce answers that look like verification and are still wrong.
Who is it for?
email-verifier suits a signup or lead-capture flow that needs to catch typos, reject disposable and role addresses and confirm the domain can receive mail, and it suits an organisation that would rather not send user addresses to a third party. It is the wrong tool when the question you need answered is whether a specific person can be reached, because it deliberately cannot answer that, and it is the wrong tool if you need a catch-all or bounce prediction.
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 21 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

A verifier built around what cannot be known

The README leads with a one paragraph description: a privacy-first, free, open source email validation API that checks syntax, DNS and MX, disposable domains and role addresses, and that does not confirm that a specific inbox exists. Addresses are not stored, and the project is MIT licensed.

That negative clause is the product. Most of what follows in the documentation is an argument for why a common feature is deliberately absent.

The service checks syntax, domain and MX records so the provider can accept mail, known disposable domains, role addresses such as admin@ and info@, alias normalization for major providers including Gmail, Yahoo and Outlook and Hotmail, and typo suggestions. It returns a derived score and a status.

What it does not check is listed with equal care: no SMTP RCPT TO or any mailbox handshake, no whether a specific mailbox or inbox exists, no catch-all domains, and no whether a message will bounce.

The status semantics are stated precisely, which is the part to read before integrating. VALID means the address is syntactically valid, the domain has usable MX, and it is not a known disposable address, and it does not mean an inbox exists. The mailbox_exists field is true when the email provider can accept mail, and the service sets it from the MX check, so it matches mx_records. A domain with only an A record, the RFC 5321 fallback, can have domain_exists true and mailbox_exists false.

The README is blunt about the consequence: an address such as [email protected] can pass the syntax and MX checks and still not be a real inbox. The reliable confirmation is double opt-in, sending a link and requiring a click.

Why the SMTP probe is left out on purpose

The section titled why we don't check SMTP is the strongest part of the documentation, because it explains the engineering rather than asserting a policy.

SMTP RCPT TO asks a mail server whether it will accept one address. The README's first point is that this reply does not prove the mailbox exists, because major providers such as Gmail, Outlook and Yahoo answer in ways that hide whether the inbox is real, so addresses cannot be harvested.

The second point is that catch-all domains accept every local-part, so a positive reply still does not mean that inbox exists. The third is that many servers block or throttle automated SMTP connections, so the check fails or lies. And the fourth is the practical one: each address needs its own live connection to a mail server, and that is too slow for this API.

The conclusion is stated as a design decision rather than a limitation. A yes would look like inbox verification and still be wrong, so this API never opens an SMTP session, and that check will not be added.

That is worth pausing on, because the market for email verification services is full of products whose headline feature is exactly the probe this project refuses to perform. A tool that says no to a check that consumers pay for, and explains what the check would actually prove, is a different kind of product.

The rest of the argument is about the limits of any check. Syntax only says the address is well formed, DNS and MX only say the domain can accept mail and say nothing about the local-part, there is no public directory of real mailboxes, and SMTP RCPT TO is not certain either even when someone runs it.

What the response actually contains

The examples in the README show the shape of a response, and they are more informative than a field list because they show how the flags combine with the status.

A standard address returns the validations object with syntax, domain_exists, mx_records and mailbox_exists all true, and the status VALID. A plus addressed address returns the same without the mailbox_exists key, and an international address in Unicode returns VALID with the same three checks. The README also shows an address with Hindi characters in both the local part and the domain as VALID.

The invalid cases are equally specific. A missing @ symbol, a double dot in the local part, a quoted local part with a space, and multiple @ symbols all return syntax false with the status INVALID_FORMAT.

The quoted string case carries a note about the validation philosophy. While email RFCs 5321 and 5322 define extensive rules for valid email formats, many of these rules are not practically enforced by modern email servers, and the project takes a practical approach focused on rules that are actually enforced by major email providers. Quoted strings in local parts are technically valid according to the RFC, but rarely supported in practice and often a cause of issues, so they are rejected.

Two flags behave differently from the status, and this is where an integration can go wrong. A disposable address returns is_disposable true and the status DISPOSABLE. A role address returns is_role_based true while the status stays VALID when syntax and MX pass and the address is not disposable, because the README is explicit that is_role_based is a flag. And an alias returns the extra field aliasOf, giving the normalized address for the provider.

No account, no limits, and a batch endpoint capped at 100

The service is hosted at rapid-email-verifier.fly.dev, with the API at the /api/validate path, and there is a website at the same host. The hosted instance has no account and no usage limits, and batch validation accepts up to 100 addresses.

The privacy claim is specific rather than absolute. GDPR, CCPA and PIPEDA are named, and the statement is that addresses are processed in memory and not saved. Combined with the statement that addresses are not stored, that is a coherent design position for an API whose input is personal data.

The batch endpoint takes a JSON body with an emails array and returns a results array carrying the same per address structure as the single check. The README notes that batch processing comes with domain-level optimizations, which matters because the expensive part of a validation is the DNS lookup, and a batch of addresses that share a domain only needs one.

The repository layout backs the operational story up. There is an openapi.yaml at the root, a config directory, a static directory, a docs directory, and a test_api.sh script for exercising the API. The Go module is called emailvalidator, requires Go 1.21, and depends on the Prometheus client library, go-redis v9, testify for tests and golang.org/x/net.

The Redis dependency is for caching rather than storage of addresses, which is the distinction that matters for the privacy claim, and the fly.toml file at the root is the deployment configuration for the hosted instance on fly.io.

Running the service with Docker Compose

The repository ships a docker-compose.yml that assembles the service with the monitoring stack around it.

yaml
  email-validator:
    image: umutert/emailvalidator:latest
    ports:
      - "8080:8080"
    environment:
      - PROMETHEUS_ENABLED=true
      - REDIS_URL=redis://redis:6379
    depends_on:
      - prometheus
      - redis

Redis is exposed on 6379 with a persistent volume and appendonly enabled, Prometheus on 9090 with the repository's prometheus.yml mounted as its config file, and Grafana on 3000 with the admin password set in the compose file and sign-up disabled. A grafana-dashboard.json at the root is the dashboard for those metrics.

The Dockerfile is a two stage build. The builder is golang:1.21-alpine, it downloads the modules, and it compiles a static binary with CGO_ENABLED=0 and GOOS=linux. The final stage is alpine:latest with the binary, the config directory, the static assets and openapi.yaml copied in, and ca-certificates installed, with a comment saying that is for secure Redis connections. The port exposed is 8080 and the command runs the binary directly.

The service configuration is therefore two environment variables, PROMETHEUS_ENABLED and REDIS_URL, with a run-docker.sh script at the root as a convenience entry point. There is also a .golangci.yml for linting and a .vscode directory, so the project is set up for local Go development rather than only for running the published image.

Where this verifier is the wrong tool

Three cases, and the first is the important one.

If the question you need answered is whether a specific person at that address can be reached, this service cannot answer it, and the README says so repeatedly. The status VALID does not mean an inbox exists, and mailbox_exists is derived from the MX check rather than from a probe of the local part. If your flow depends on that distinction, the answer is double opt-in, sending a link and requiring a click, and no amount of validation shortens that.

The second is catch-all and bounce behaviour. A domain that accepts every local-part will pass the MX check, and the service does not attempt to detect that case, so an address at a catch-all domain will look valid. Whether a message will bounce is also explicitly outside what is checked, which means the tool cannot be used to prune a list against a bounce expectation.

The third is the coverage of the normalisation features. Alias detection is for major providers, named as Gmail, Yahoo and Outlook and Hotmail, and role address detection is a flag rather than a status, so an address like [email protected] is still reported as VALID. Typo suggestions and disposable detection are as good as their underlying lists, and the repository does not describe how those lists are sourced or how current they are.

For a signup form that wants to catch fat fingers, that is exactly the right tool. For a system that needs to know who is reachable, it is the wrong one.

Licence, maintenance and how to judge it

The licence is MIT, so the code can be used, modified and redistributed with attribution and no other obligation, and the hosted service and the self-hosted image are both free of charge according to the project description.

The maintenance picture is modest but not stale. The last push was on 2026-10-02 and the repository is not archived, although there are no GitHub releases, so the version reference is the container image and the source tree rather than a tagged release. The hosted endpoint is a live instance on fly.io, and the compose file builds against a published image tagged latest, which is worth pinning in your own deployment.

The privacy position is the part to test rather than trust, because it is a claim about a running service as much as about the code. The stated design is that addresses are processed in memory and not saved, the Redis dependency exists for caching DNS and MX results rather than for storing addresses, and the repository has no database. If you are handling personal data under GDPR or similar, self-hosting it and reading the configuration is the way to satisfy your own review, and the repository gives you a Docker Compose stack for exactly that.

The other thing to check before adopting it is the openapi.yaml at the root, which is the contract you would code against. The README examples show the response shape for single and batch calls, and the specification is the authoritative version of that contract.

Editorial conclusion

email-verifier suits a signup or lead-capture flow that needs to catch typos, reject disposable and role addresses and confirm the domain can receive mail, and it suits an organisation that would rather not send user addresses to a third party. It is the wrong tool when the question you need answered is whether a specific person can be reached, because it deliberately cannot answer that, and it is the wrong tool if you need a catch-all or bounce prediction. Verify first that your assumptions match the response semantics, in particular that mailbox_exists is derived from the MX check rather than an independent probe, and read the batch endpoint contract, which caps a request at 100 addresses and returns per address results.

Frequently asked questions

Does email-verifier check whether an inbox actually exists?

No. It never opens an SMTP session and does not perform an RCPT TO probe, because the project argues that a positive reply would still not prove existence, that catch-all domains accept every address, that providers hide real mailboxes and that many servers block automated probes. The README names double opt-in as the reliable confirmation.

What does the VALID status actually mean?

It means the address is syntactically valid, the domain has usable MX records and it is not a known disposable address. The mailbox_exists field is set from the MX check, so it matches mx_records and does not prove that the local part is a real mailbox.

How are disposable and role addresses reported?

A disposable address returns is_disposable true and the status DISPOSABLE. A role address such as [email protected] returns is_role_based true while the status stays VALID when the syntax and MX checks pass and the address is not disposable, because is_role_based is a flag rather than a status.

Does email-verifier store the addresses I check?

The README states that addresses are processed in memory and not saved, and names GDPR, CCPA and PIPEDA compliance, so the service is designed not to retain personal data. The repository contains no database, and the Redis dependency is used for caching rather than storage.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. umuterturk/email-verifier on GitHub
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/umuterturk-email-verifier.svg)](https://hysenlabs.com/projects/umuterturk-email-verifier)

Was this useful?