Self-hosted service
reacherhq/check-if-email-exists avatar
reacherhq/check-if-email-exists

check-if-email-exists: SMTP-level email verification in Rust

Check if an email address exists without sending any email, written in Rust. Comes with a ⚙️ HTTP backend.

10,042 stars726 forksRustNOASSERTION

At a glance

What is it?
check-if-email-exists is a Rust library, CLI and HTTP backend that probes an address through syntax, MX and SMTP checks without sending mail. It is strongest as an embeddable verifier, and weakest when you have no outbound port 25 or no proxy pool.
Who is it for?
Adopt check-if-email-exists if you need verification logic inside a Rust service, or a self-hosted /v0/check_email endpoint on a host with outbound port 25 and, at any real volume, SMTP proxies. Do not adopt it if you cannot open port 25, if you want a hosted API with no operational work, or if you expect a drop-in Python or npm package, since the README lists only Rust, CLI and Docker paths.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

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

Editorial analysis

What check-if-email-exists actually decides

The project answers one question: how likely is it that mail sent to this address will be accepted? The README describes the top-level answer as is_reachable, with four possible values: safe, risky, invalid and unknown. That vocabulary matters more than any single check, because it admits uncertainty instead of pretending verification is binary.

The checks behind it are layered. syntax.is_valid_syntax covers RFC-style address shape and can return a suggestion for a likely typo. mx.accepts_mail covers whether the domain publishes usable MX records. smtp.can_connect_smtp and smtp.is_deliverable cover the conversation with the mail exchanger. misc.is_disposable, misc.is_role_account and misc.is_b2c classify the address rather than test it.

The audience is developers who already own a signup or outreach pipeline and want a verification step inside it, not a marketing team shopping for a dashboard. The README's own example output is instructive: for [email protected] it reports is_reachable as invalid with smtp.is_disabled true, which is the honest answer for a disabled Gmail mailbox rather than a guess.

How the SMTP probe works and where the JSON comes from

The flow is a chain, and each stage can short-circuit the next. The address is parsed and validated; the domain is resolved for MX records; then the tool opens an SMTP connection to the mail exchanger and asks about the recipient. The README notes that outbound port 25 must be open for the Docker backend, which is the constraint that follows directly from that design: this is a real SMTP conversation, not an HTTP lookup against a third-party database.

The output is a single JSON object with four blocks: input, is_reachable, misc, mx, smtp and syntax. The smtp block carries can_connect_smtp, has_full_inbox, is_catch_all, is_deliverable and is_disabled. is_catch_all is the field that saves you from the most common false positive: a domain that accepts every recipient at the SMTP layer, so a successful probe proves nothing about that specific mailbox.

The workspace layout in Cargo.toml mirrors the deployment options: members are backend, cli, core and sqs. Core is the verification engine, cli wraps it for local use, backend exposes HTTP, and sqs suggests a queue-driven worker path. The README also states the CLI binary does not connect to any backend and checks the email directly from your computer, so the CLI and the HTTP backend are two independent front ends over the same core logic.

Installing the Docker backend and sending a first check

The README calls the HTTP backend the popular method and gives a single Docker command. Run it on a host where outbound port 25 is open, or the SMTP stage will fail while syntax and MX checks still return.

bash
docker run -p 8080:8080 reacherhq/backend:latest

The container listens on 8080. Once it is up, send a POST request to the check endpoint with the address in the to_email field. The README's example body also shows an optional proxy object with host, port, username and password fields, which is how you route the SMTP conversation through a SOCKS5 proxy instead of your own IP.

js
{
    "to_email": "[email protected]",
    "proxy": {
        "host": "my-proxy.io",
        "port": 1080,
        "username": "me",
        "password": "pass"
    }
}

The response is the JSON structure described above. Check is_reachable first, then look at smtp.is_catch_all before trusting a positive is_deliverable.

If you would rather embed the engine, the README shows the Rust dependency and a minimal call. Note that the README pins the dependency as version 0.9 while the latest release listed is v0.11.7, so confirm the version you actually resolve.

toml
[dependencies]
check-if-email-exists = "0.9"

The README's usage snippet constructs a CheckEmailInput from a vector of addresses and awaits check_email, which returns a Vec<CheckEmailOutput>. There is also a CLI binary downloadable from the releases page, whose help output the README quotes as check_if_email_exists 0.9.1.

Port 25, proxies and the volume ceiling

The hard limitation is infrastructural. Many cloud providers block outbound port 25 by default, and without it the SMTP stage cannot run, leaving you with syntax and MX results only. The README acknowledges this directly by requiring the port to be open for the Docker backend.

The second limitation is scale. The README states that operating with your own IP addresses is possible, but that processing more than very small volumes requires SMTP proxy servers, and points to a commercial proxy provider. That is an operational dependency, not a code problem: verification traffic from a single IP will be rate-limited or blocked by large mail providers, and the project does not ship a proxy pool.

Catch-all domains are the third failure mode. When is_catch_all is true, a positive SMTP response tells you the domain accepts mail, not that the mailbox exists. Any pipeline that treats is_deliverable as ground truth without reading is_catch_all will accumulate bad addresses.

Finally, this is the wrong tool if you want a managed answer with no servers. The README itself markets a hosted option at reacher.email and a SaaS product, which is a fair signal that the self-hosted path carries real operational weight.

How it compares with hosted verification APIs

The obvious alternative is a hosted verification API, including the one the README promotes at reacher.email and No2Bounce.com. The difference in approach is where the SMTP conversation happens. With a hosted service, the vendor owns the IP pool, the port 25 egress and the reputation management, and you send an HTTP request and get a verdict back. With check-if-email-exists self-hosted, you own all of that, which is exactly why the README's proxy note exists.

That trade-off cuts both ways. Self-hosting means the address list never leaves your infrastructure, which matters if you are verifying your own customer base rather than purchased lists. It also means your throughput is bounded by the egress you can arrange. A hosted API inverts both: less control, less work.

For a Rust service that already runs its own infrastructure, embedding the library removes a network hop and a per-address cost. For a team without a host that can open port 25, the Docker backend is not a shortcut around the hosted option; it is a different set of obligations.

Licensing and the cost of running it

The repository ships LICENSE.AGPL and LICENSE.md alongside each other, and the metadata records the licence as NOASSERTION, which means the repository does not declare a single SPDX identifier you can rely on at a glance. The AGPL filename is the signal that matters for anyone embedding this in a network service. Read both files and get your own advice before shipping; the README's own split between an open-source tool and a commercial SaaS offering suggests the licensing boundary is deliberate.

The Makefile also reveals a commercial licence trial path: a run-with-commercial-license-trial target sets RCH__COMMERCIAL_LICENSE_TRIAL__URL. That implies licence enforcement exists in the backend, and that a paid path is anticipated for some deployments.

Upgrade cost is moderate. Releases are infrequent rather than constant, with v0.11.7 in January 2026, v0.11.6 in July 2025 and v0.11.5 in April 2025. The last push to the repository was on 2026-03-17. The workspace spans four crates plus a backend with Postgres and RabbitMQ integrations, so a version bump touches more than one binary. The worker mode in the Makefile requires a Postgres database and a RabbitMQ instance, which is the part of the upgrade surface most likely to bite.

Editorial conclusion

Adopt check-if-email-exists if you need verification logic inside a Rust service, or a self-hosted /v0/check_email endpoint on a host with outbound port 25 and, at any real volume, SMTP proxies. Do not adopt it if you cannot open port 25, if you want a hosted API with no operational work, or if you expect a drop-in Python or npm package, since the README lists only Rust, CLI and Docker paths. Before committing, verify three things: that your provider allows outbound 25, that the AGPL and commercial terms in LICENSE.AGPL and LICENSE.md fit your distribution model, and that the JSON fields is_reachable and smtp.is_deliverable give you the confidence split your pipeline needs.

Frequently asked questions

Can check-if-email-exists tell me if an email address is still active?

It reports is_reachable as safe, risky, invalid or unknown, and the smtp block includes is_deliverable and is_disabled. For the README's own example, [email protected] returns is_reachable as invalid with smtp.is_disabled true. Treat the result as a confidence level rather than a guarantee.

Can I ping an email address to see if it exists?

The tool opens an SMTP connection to the domain's mail exchanger and asks about the recipient, which is the closest thing to a ping that email supports. The README notes outbound port 25 must be open for the Docker backend, since that connection is the probe.

How can I tell if a Gmail address exists with check-if-email-exists?

Send the address to the backend's check endpoint and read the smtp block. The README's sample output for [email protected] shows the Gmail MX records listed under mx.records, with smtp.is_disabled true for that particular disabled mailbox.

How can I verify if an email address is real?

The README lists syntax validation, DNS MX record validation, SMTP server validation and email deliverability as the checks performed, with the combined verdict in is_reachable. Catch-all domains are flagged separately in smtp.is_catch_all, so a positive deliverability result should be read alongside it.

How do I check if an email exists without sending any email?

Run the Docker backend on a host with outbound port 25 open and POST the address to the check endpoint in the to_email field. The SMTP stage only asks the mail exchanger about the recipient, so no message is sent to the address.

Official sources

  1. Issues
  2. Project website
  3. reacherhq/check-if-email-exists on GitHub
  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/reacherhq-check-if-email-exists.svg)](https://hysenlabs.com/projects/reacherhq-check-if-email-exists)