Fast, statically linked email list validator in Rust — CLI pipeline & HTTP API.
Email Validator extracts, deduplicates, and validates email addresses from any text source (TXT, Markdown, XML, CSV, STDIN pipes). Three validation modes: regex only, MX lookup, or full SMTP handshake. Ships as a single static binary with both a CLI pipeline and a built-in HTTP API server.
# Simplest usage: file in, validated list out
email_validator run -i input.txt -o verified.txt
# Regex-only validation (no network)
email_validator run -i mails.txt -m regex
# GoPhish CSV output format
email_validator run -i mails.txt -o out.csv -f gophish
# JSON output for scripting / automation
email_validator run -i mails.txt -j | jq
# Via pipe (STDIN)
cat mails.txt | email_validator run -f listThe binary includes a built-in HTTP API via the api subcommand:
# Start the server (defaults to 0.0.0.0:8080)
email_validator api
# Custom bind address
email_validator api 127.0.0.1:3000
# Or via environment variable
BIND_ADDR=0.0.0.0:9000 email_validator api| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check — returns {"status":"ok","version":"0.4.2"} |
GET |
/validate?email=…&method=… |
Validate a single email |
POST |
/validate |
Batch validate up to 1000 emails |
{
"emails": ["alice@example.com", "bogus", "bob@test.de"],
"method": "regex",
"disable_wildcard": false
}{
"total": 3,
"valid_count": 2,
"invalid_count": 1,
"catch_all_count": 0,
"results": [
{ "email": "alice@example.com", "valid": true },
{ "email": "bogus", "valid": false },
{ "email": "bob@test.de", "valid": true }
]
}- Emails are deduplicated case-insensitively before validation
- Max 1000 emails per request — returns HTTP 400 if exceeded
- Empty list returns HTTP 400
methodanddisable_wildcardare optional (default:smtp/false)
# Health check
curl http://localhost:8080/health
# → {"status":"ok","version":"0.4.2"}
# Single email (GET)
curl "http://localhost:8080/validate?email=test@example.com&method=regex"
# → {"total":1,"valid_count":1,"invalid_count":0,"catch_all_count":0,"results":[{"email":"test@example.com","valid":true}]}
# Batch — Regex (no network, instant)
curl -X POST http://localhost:8080/validate \
-H "Content-Type: application/json" \
-d '{"emails":["alice@example.com","bogus","bob@test.de"],"method":"regex"}'
# → Syntax-only check, valid JSON emails pass, "bogus" fails
# Batch — MX (DNS lookup)
curl -X POST http://localhost:8080/validate \
-H "Content-Type: application/json" \
-d '{"emails":["user@gmail.com","nobody@this-domain-does-not-exist-12345.com"],"method":"mx"}'
# → Checks MX records of each domain
# Batch — SMTP (default, full handshake)
curl -X POST http://localhost:8080/validate \
-H "Content-Type: application/json" \
-d '{"emails":["info@example.com","fake@nonexistent.invalid"]}'
# → Full SMTP handshake: HELO → MAIL FROM → RCPT TO → QUIT
# Batch — SMTP with wildcard detection
curl -X POST http://localhost:8080/validate \
-H "Content-Type: application/json" \
-d '{"emails":["user@catch-all-domain.com"],"method":"smtp"}'
# → Pre-checks domain for catch-all behavior (catch_all: true in response)
# Error: empty list → 400
curl -X POST http://localhost:8080/validate \
-H "Content-Type: application/json" \
-d '{"emails":[]}'
# → {"error":"email list is empty"}
# Error: too many emails → 400
curl -X POST http://localhost:8080/validate \
-H "Content-Type: application/json" \
-d '{"emails":["a@b.de","c@d.de", ...]}'
# → {"error":"too many emails, max 1000"} (checked before any processing)
# Error: malformed JSON → 422
curl -X POST http://localhost:8080/validate \
-H "Content-Type: application/json" \
-d '{broken'
# → 422 Unprocessable Entity (axum auto-rejects invalid JSON)# Containerfile
FROM cgr.dev/chainguard/wolfi-base:latest
COPY email_validator /usr/local/bin/email_validator
EXPOSE 8080
USER nonroot
ENTRYPOINT ["email_validator", "api"]# docker-compose.yml
services:
email-validator:
image: ghcr.io/evait-security/email_validator:latest
ports:
- "8080:8080" # Host:Container
restart: unless-stopped
read_only: true
# Optional: auto-update container on new releases
watchtower:
image: containrrr/watchtower:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 300 email-validator
restart: unless-stoppedThe image is built automatically on every release and pushed to
ghcr.io/evait-security/email_validator. Add Watchtower to auto-update.
The binary is statically linked (musl) and runs on any Linux x86_64 — Alpine, Wolfi, Arch, Debian, Ubuntu, CentOS, embedded systems. No glibc, no runtime dependencies.
Simply make it executable and go:
chmod +x email_validator
./email_validator run -i emails.txt -o clean.txt| Flag | Description | Default |
|---|---|---|
-i |
Input file (optional, STDIN otherwise) | — |
-o |
Output file (optional, STDOUT otherwise) | — |
-m |
Validation method: regex, mx, smtp |
smtp |
-f |
Output format: list, gophish |
list |
-j |
JSON array output (conflicts with -f) |
false |
-d |
Disable wildcard domain check | false |
-v |
Verbose mode | false |
| Arg / Env | Description | Default |
|---|---|---|
[BIND_ADDR] |
Address to bind (positional or $BIND_ADDR env) |
0.0.0.0:8080 |
-v |
Verbose STDERR logging | false |
# All equivalent:
email_validator api
email_validator api 127.0.0.1:3000
BIND_ADDR=127.0.0.1:3000 email_validator api| Method | Description | Network |
|---|---|---|
regex |
Syntax check via RFC-compliant regex | ❌ |
mx |
Regex + MX record lookup of domain | ✅ |
smtp |
Regex + MX + SMTP handshake (RCPT TO) | ✅ |
| Flag | Format | Includes |
|---|---|---|
-f list (default) |
One valid email per line | Valid only |
-f gophish |
CSV: First Name,Last Name,Email,Position |
Valid only |
-j / --json |
JSON array | All emails (valid + invalid) |
Designed for scripting, automation pipelines, and API consumption. Each email is an object
with email, valid, and optionally catch_all (only present when true):
[
{ "email": "alice@example.com", "valid": true },
{ "email": "bob@catch-all.tld", "valid": true, "catch_all": true },
{ "email": "nobody@no-mx-xyz123.de", "valid": false }
]Use with jq for filtering and transformation:
# Extract only valid emails
email_validator -i mails.txt -j -m smtp | jq '[.[] | select(.valid)]'
# Pipe directly into a webhook or file
email_validator -i mails.txt -j -o result.jsonThe regex parser reliably extracts emails from:
- TXT — prose, lists, CSV exports
- Markdown — links, code blocks, tables,
mailto:links - XML — attributes, CDATA sections, text nodes
- HTML — tags, attributes, plaintext
- Any mixed content with noise, special characters, and broken entries
Duplicates (including case-insensitive variants) are automatically detected and removed.
# Clone the repository
git clone https://github.com/USERNAME/email_validator.git
cd email_validator
# Build a static binary (requires musl toolchain)
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
# Optional: compress with UPX (~8 MB → ~3 MB)
upx --best --lzma target/x86_64-unknown-linux-musl/release/email_validatorRun tests (53 total, all green ✅):
cargo test# Regenerate diagrams (requires plantuml)
cd doc && plantuml *.pumlModule-level docs for all internal types and functions.
👉 Developer Docs — live on GitHub Pages.
cargo doc --no-deps --openA tarball of the docs is also attached to every release
as email_validator_docs.tar.gz.
This opens a local browser with docs for ingestion, precheck,
validation, output, and all public types.
This project is licensed under the MIT License.
You may copy, modify, distribute, and use it in your own projects (including commercial software) with proper attribution.
- Rust (Edition 2024)
- musl — fully static linking
- UPX — binary compression for minimal download size
- axum — HTTP API server
- Property-Based Testing via
proptestfor fuzzing Markdown/XML/noise inputs - CI/CD via GitHub Actions (tests + automatic release)


