Library / SDK
FGRibreau/mailchecker avatar
FGRibreau/mailchecker

MailChecker ships one domain list to nine languages, and generation is the maintenance model

:mailbox: Cross-language temporary (disposable/throwaway) email detection library. Covers 55 734+ fake email providers.

1,914 stars316 forksPHPMIT

At a glance

What is it?
MailChecker is a cross-language library that rejects disposable email domains, backed by a shared list of more than 55000 domains that is compiled into each platform implementation. It suits a signup flow that needs a cheap second filter on top of format validation, and it does not suit anyone needing deliverability checks, since it never contacts an MX server.
Who is it for?
Adopt MailChecker as a low-cost second filter in a signup flow where disposable addresses cause real downstream cost, and pick the language binding your stack already has. Do not adopt it as your primary email validation, because it checks syntax and a domain list and does not verify that the mailbox exists or accepts mail.
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 2 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

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

From daily snapshots

Star history

  • FGRibreau/mailchecker1,914 ★ · +1
Star history
Repository2026-10-162026-10-22
FGRibreau/mailchecker1.9k1.9k

1 Stars in the last 6 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The problem: an address that passes validation and still goes nowhere

The stated use case is narrow and practical. The README frames MailChecker as helpful when you have to contact your users and want to avoid errors causing lack of communication, or when you want to block spamboxes.

That is a real and specific failure mode. A signup form can accept a syntactically perfect address at a domain whose entire purpose is to expire, and the failure surfaces days later when your activation email disappears. By then the user account exists and your cost has already been paid.

MailChecker does two things. It validates the format of the address, and it validates that the address is not a temporary mail domain, with yopmail given as the example. The README invites you to add your own dataset to `list.txt`, which is the extension mechanism.

Format validation is delegated rather than reinvented. Per the README, the regex comes from validator.js, and PHP uses `FILTER_VALIDATE_EMAIL`. That is a sensible division: there is no reason to maintain your own email regex when a maintained one exists, and the disposable check is the part that genuinely needs this project's dataset.

The backing data is `list.txt`, which the README describes as a database of over 55 000 throwable email domains, and the repository description says it covers 55 734+ fake email providers. That number is the product. The code is a thin shell around it.

Nine platforms, one list, and a code generator

The unusual thing about MailChecker is not that it detects disposable email. It is that the dataset is shipped identically to nine language ecosystems from one repository.

The README lists NodeJS, JavaScript for client-side use, PHP, Python, Ruby, Rust, Elixir, Clojure and Go. Each lives under `platform/<name>` with its own installation instructions.

The repository layout explains how that stays consistent. `lib/` and `gen.js` are the build side: the npm `build` script runs `node lib/clean.js && node gen.js`, so the platform implementations are generated from the shared source rather than written by hand. `list.txt` at the repository root is the input, and `types.d.ts` is shipped alongside the Node entry point for TypeScript consumers.

The npm package metadata shows the blast radius of one dataset. `main` is `platform/node/index.js`, and `files` lists exactly two paths, that entry point and `types.d.ts`. The version is 6.0.21.

There is also a template system for adding a language, and the README invites pull requests for new platforms. That is a reasonable alternative to maintaining nine client libraries in nine separate repositories, and it is why the list can change in one commit instead of nine pull requests.

The cost of that model is that every platform shares the same blind spots. A bug in the matching logic is a bug in all nine implementations.

API changes between major versions, and what 3.x to 4.x did to PHP

The README documents an upgrade path with a normalized public API, and the changes are the kind that break compilation rather than behaviour.

Going from 1.x to 3.x, the constructor-call API was replaced with a static call in two languages. In NodeJS and JavaScript, `MailChecker(email)` became `MailChecker.isValid(email)`. In PHP, `MailChecker($email)` became `MailChecker::isValid($email)`. In Python the change was structural rather than one line: constructing an instance first.

python
import MailChecker
m = MailChecker.MailChecker()
if not m.is_valid('[email protected]'):
  # ...

became:

python
import MailChecker
if not MailChecker.is_valid('[email protected]'):
  # ...

Going from 3.x to 4.x, the README gives only the PHP note, and it is an import problem rather than an API problem:

php
# import using the fully qualified name.
use Fgribreau\MailChecker;

// ...
echo MailChecker::isValid('[email protected]');

If you upgrade PHP across that boundary and nothing resolves, the cause is a missing `use` statement with a namespace, not a renamed method.

The Go module path carries its own version marker, `github.com/FGRibreau/mailchecker/v6`, which means Go users must use `/v6` in their import paths and cannot rely on the old module path resolving. That is standard Go module practice but it is a hard break for anyone upgrading across a major version.

Check your call sites before upgrading rather than after, since every one of these changes fails loudly.

Calling it from PHP and NodeJS

The PHP entry point is a static method, used here against an address at yopmail.com, which is the disposable case:

php
echo MailChecker::isValid('[email protected]');

The NodeJS package is imported with `require` and calls the same static method:

javascript
var MailChecker = require('mailchecker');

The README's NodeJS snippet is cut off at that point, so the full conditional is not visible here. What is visible is the shape: a CommonJS require, a static `isValid` call, and a negated result feeding a rejection path.

Note that the npm package name is `mailchecker` while the PHP namespace is `Fgribreau\MailChecker` and the Ruby gem is `ruby-mailchecker`, and the Packagist package is `fgribreau/mailchecker`. Four different identifiers for one dataset, which is the practical thing to check when searching for the right package in each ecosystem.

The client-side JavaScript build is listed as a separate platform directory from the NodeJS one. That distinction exists so a browser bundle does not pull in Node-only code, and it means the JavaScript variant may lag the NodeJS one if a recent change touched the generator.

How testing works, and what it reveals about the toolchain

The npm scripts show a project that tests across nine languages using Docker containers, and the image tags are the interesting part.

Each language has a setup and a test script. Ruby setup pins `ruby:2.2` with `bundler -v 1.17.3`. Clojure uses `clojure:lein-2.8.1`. Rust uses `rust:1.74.0-slim-buster`. Elixir tests use `elixir:1.6-slim`. Go tests use `golang:1.13-alpine3.10`. PHP tests use `pipelinecomponents/phpunit` with `XDEBUG_MODE=coverage`. Python tests run on both `python:2.7-alpine3.7` and `python:3.6-alpine3.7`.

Those are old images, and they are old on purpose: they pin the oldest supported runtime per language, so the test suite catches anything that breaks on the floor of the support matrix. That is a defensible choice for a compatibility library, and it is also a clear statement about what the code has to keep compiling against.

One script tells a story about the dataset size. The Clojure test line begins with an echo stating that it is skipping the clojure test because it cannot handle code too large. A 55 000-entry list compiled into nine implementations is enough to exceed what one of those toolchains can load.

The Go module declares `go 1.13`, matching the `golang:1.13-alpine3.10` test image. So the Go binding is built and tested against a Go version from 2019, and it is a single require block with no dependencies at all.

Test ordering is serial for tests (`--max-parallel 1`) but parallel for setup (`--max-parallel 2`), and Node's mocha run uses a 10-second timeout with a 10-second value, which suggests the test fixture set is not tiny.

Maintenance signals, and where this approach stops working

The maintenance picture has a specific shape worth describing.

The last push was on 2026-10-12, so commits are current. The README carries a badge reading actively maintained, and a Codementor badge offering advisory availability. The npm package is at 6.0.21. But the repository has no GitHub releases at all, which means there is no changelog-with-tags to diff against and no release note to read before upgrading. The `CHANGELOG.md` exists, and `.changelog.hbs` suggests it is generated from a template.

Now the limits of the approach.

It does not check deliverability. There is no MX lookup, no SMTP probing, no catch-all detection. An address at a real domain belonging to someone else passes. That class of problem needs a different tool, and mixing the two concerns is where expectations get confused.

It is a domain list, so it is a lagging indicator. A disposable provider that launches today is not blocked today. Conversely, a legitimate domain that hosts a lot of user mail can end up on the list, which is why the README asks for dataset additions rather than presenting the list as authoritative.

And blocking is a policy decision with a cost. If your users include students, contractors or people in privacy-conscious countries, disposable domains are often exactly what they use, and a hard reject loses real signups. A warn-and-proceed or a delayed-send flow is often better than a block, which is something the library's boolean API does not express but your application layer can.

Editorial conclusion

Adopt MailChecker as a low-cost second filter in a signup flow where disposable addresses cause real downstream cost, and pick the language binding your stack already has. Do not adopt it as your primary email validation, because it checks syntax and a domain list and does not verify that the mailbox exists or accepts mail. Before wiring it in, read `list.txt` and confirm the domains your own users legitimately use are absent, and treat the absence of GitHub releases as the release process to plan around: the npm package is at 6.0.21 and the Go module is `github.com/FGRibreau/mailchecker/v6`, so version pinning is your responsibility.

Frequently asked questions

What is MailChecker and what does it detect?

It validates the format of an email address and checks whether the domain is a temporary or disposable mail provider, using a shared list of more than 55 000 throwable domains. Format validation uses the validator.js regex and `FILTER_VALIDATE_EMAIL` in PHP.

Which languages does MailChecker support?

NodeJS, JavaScript for the browser, PHP, Python, Ruby, Rust, Elixir, Clojure and Go. Each implementation lives under `platform/<name>` and is generated from the shared source by `gen.js`.

How do I check an email address with MailChecker in PHP?

Import the namespace with `use Fgribreau\MailChecker;` and call the static method, for example `echo MailChecker::isValid('[email protected]');`. The `use` statement is required from version 3.x to 4.x.

Does MailChecker verify that a mailbox exists?

No. It validates syntax and checks the domain against a list. It performs no MX lookup or SMTP check, so an address at a real domain belonging to someone else will pass.

How do I add a disposable email domain to MailChecker's list?

The README links to `list.txt` in the repository and asks that your own dataset be added there. Because the platform implementations are generated from that file by `gen.js`, a change to the list propagates to every language binding on the next build.

What version am I importing in Go?

The module path is `github.com/FGRibreau/mailchecker/v6`, so the major version suffix is part of the import path. The `go.mod` declares `go 1.13` and the Go binding has no dependencies.

Official sources

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

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/fgribreau-mailchecker.svg)](https://hysenlabs.com/projects/fgribreau-mailchecker)