Tiny Go and Rust libraries, plus a Go CLI, that answer one question: "Is this email's domain a disposable / one-time mailbox?"
- ~213k known disposable domains, merged from nine community lists and embedded into the binary (no network or disk I/O at runtime).
- O(1) exact-match lookup through an in-memory map; wildcard suffixes use a short linear scan. Safe for concurrent use.
- Hourly updates from upstream lists, with per-source license verification
on every run (see
.github/workflows/update.yml). - MIT licensed.
Maintained by BillionVerify, where the same disposable-domain data powers the production /v1/verify/disposable endpoint.
go get github.com/billionverify/disposableOr grab the CLI:
go install github.com/billionverify/disposable/cmd/disposable-check@latestInstall the native disposable crate from crates.io:
cargo add disposableimport "github.com/billionverify/disposable"
if disposable.IsDomain("mailinator.com") {
// refuse, throttle, or tag as low-trust
}
if disposable.IsEmail("user@mailinator.com") {
// ...
}
fmt.Println("loaded:", disposable.Count(), "disposable domains")Hot-swap the table at runtime (e.g. fed by your own internal blocklist):
disposable.SetData(
[]string{"trashy.example", "also-bad.example"}, // exact-match
[]string{"*.tempmail.example"}, // wildcard suffixes
[]string{"good.example"}, // exceptions: never flag these
)ResetEmbedded() reverts to the embedded list.
if disposable::is_domain("mailinator.com") {
// refuse, throttle, or tag as low-trust
}
if disposable::is_email("user@mailinator.com") {
// ...
}
println!("loaded: {} disposable domains", disposable::count());Build an immutable detector from your own newline-delimited lists:
use disposable::DisposableDomains;
let domains = DisposableDomains::from_lists(
"trashy.example\nalso-bad.example",
"*.tempmail.example",
"good.example",
);
assert!(domains.is_domain("also-bad.example"));- Input is case-insensitive (
MAILINATOR.com≡mailinator.com). - Whitespace is stripped.
- IDN is converted to ASCII via
golang.org/x/net/idnain Go andidnain Rust before lookup. - Resolution order: exceptions → exact domain → wildcard suffix.
- A wildcard entry
*.foo.examplematches bothfoo.exampleand any subdomain (a.foo.example,a.b.foo.example).
$ disposable-check user@mailinator.com alice@example.com
disposable user@mailinator.com
clean alice@example.com
$ echo user@mailinator.com | disposable-check --quiet || echo "BAD"
BAD
$ disposable-check --count
212947Exit codes:
| code | meaning |
|---|---|
0 |
every input is clean |
1 |
at least one input is disposable |
2 |
usage / I/O error |
data/
domains.txt # one disposable domain per line (≈ 213k entries)
wildcards.txt # one suffix per line, optional `*.` prefix
exceptions.txt # never-flag list (overrides domains.txt)
Lines starting with # are treated as comments. Each file is embedded into the binary at build time via Go's go:embed and Rust's include_str!.
Fast path — open a PR that edits the relevant file by hand:
echo new-bad-domain.example >> data/domains.txt
sort -u -o data/domains.txt data/domains.txt
go test ./...
cargo test --all-targetsBulk path — the hourly GitHub Actions workflow at
.github/workflows/update.yml verifies each upstream's license, pulls its
data, normalizes (lowercase + IDN-to-ASCII + dedup), drops malformed
entries, and commits the result straight to main if anything changed.
See CONTRIBUTING.md for the curation policy and scripts/sources.json
for the full source list.
Go v0.YYYY.MMDD patch releases ship when the domain table changes — pin a version in go.mod if you need reproducibility. The Go API is stable; only the embedded data churns.
The Rust crate follows Cargo semantic versioning and is published independently on crates.io.
See RELEASE.md for the pre-tag checklist.
- Free:
/v1/verify/disposabledoes not consume credits. - Documentation: Disposable Email Check API reference.
If you want to skip embedding the list, call the hosted BillionVerify endpoint directly. It performs only the disposable-domain lookup; it does not call SMTP, MX, or the verification cache.
Disposable email check — POST https://api.billionverify.com/v1/verify/disposable:
curl -X POST https://api.billionverify.com/v1/verify/disposable \
-H "BV-API-KEY: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "user@mailinator.com"}'The endpoint also supports GET /v1/verify/disposable?email=user%40mailinator.com. Invalid email syntax returns HTTP 400 rather than reporting is_disposable: false, and check_smtp: true is rejected because this endpoint is lookup-only.
Response:
{
"success": true,
"code": "0",
"message": "Success",
"data": {
"email": "user@mailinator.com",
"domain": "mailinator.com",
"is_disposable": true,
"checked_at": "2026-08-15T08:30:00Z"
}
}Equivalent from Go:
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type disposableResponse struct {
Data struct {
Email string `json:"email"`
Domain string `json:"domain"`
IsDisposable bool `json:"is_disposable"`
CheckedAt string `json:"checked_at"`
} `json:"data"`
}
func main() {
body, _ := json.Marshal(map[string]string{"email": "user@mailinator.com"})
req, _ := http.NewRequest("POST",
"https://api.billionverify.com/v1/verify/disposable",
bytes.NewReader(body))
req.Header.Set("BV-API-KEY", os.Getenv("BV_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result disposableResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.Data.IsDisposable)
}Initial seed list compiled from years of internal abuse fighting at BillionVerify, augmented hourly by these MIT / BSD / CC0-licensed community projects:
- disposable-email-domains/disposable-email-domains (CC0-1.0)
- FGRibreau/mailchecker (MIT)
- 7c/fakefilter (BSD-3-Clause)
- amieiro/disposable-email-domains (MIT)
- ivolo/disposable-email-domains (MIT)
- wesbos/burner-email-providers (MIT)
- martenson/disposable-email-domains (CC0-1.0)
- groundcat/disposable-email-domain-list (MIT)
- unkn0w/disposable-email-domain-list (MIT)
See THIRD_PARTY_NOTICES.md for attribution details.