emailval-client
Python client for emailval.net — email validation API.
$0.0008 per email. 10× cheaper than ZeroBounce.
Installation
pip install emailval-client
Requires Python 3.10+. The only dependency is httpx.
Quickstart
from emailval import EmailValidator
ev = EmailValidator(api_key="your_api_key")
results = ev.validate(["user@company.com", "test@mailinator.com", "bad-address"])
for r in results:
print(r.email, r.score, r.is_valid)
Output:
user@company.com 100 True
test@mailinator.com 20 False
bad-address 0 False
Get your API key at emailval.net — self-serve, no sales call.
Five-layer validation pipeline
Every email passes through five layers:
| Layer | Check | Speed |
|---|---|---|
| 1 | RFC 5321/5322 syntax | <1ms |
| 2 | MX record lookup | ~50ms |
| 3 | Disposable domain (350k+ blocked domains) | <1ms |
| 4 | Catch-all detection | ~200ms |
| 5 | SMTP mailbox verification | ~300ms |
Returns a deliverability score from 0 to 100:
| Score | Meaning |
|---|---|
100 |
Valid syntax + MX + not disposable + not catch-all + SMTP confirmed |
60 |
Valid, but provider blocks SMTP verification (Gmail, Outlook, Yahoo) |
20 |
Disposable or catch-all domain |
5 |
Valid syntax but no MX records |
0 |
Invalid syntax |
result.is_valid returns True for score ≥ 60 — this threshold covers Gmail
and Outlook addresses, which always return 60 because those providers block
SMTP verification by design.
Pricing comparison
| Service | Price per email | Relative cost |
|---|---|---|
| ZeroBounce | $0.0080 | 10× more expensive |
| NeverBounce | $0.0070 | 8.7× more expensive |
| MillionVerifier | $0.0030 | 3.7× more expensive |
| emailval | $0.0008 | ✓ baseline |
No monthly fee. No minimum. Stripe charges your card at the end of each billing period for exactly what you used.
Usage
Filter valid emails from a list
from emailval import EmailValidator
ev = EmailValidator(api_key="your_api_key")
results = ev.validate(your_list)
valid_emails = [r.email for r in results if r.is_valid]
print(f"{len(valid_emails)} / {len(your_list)} are valid")
Get full metadata per email
for r in results:
print(r.email)
print(f" score: {r.score}")
print(f" valid_syntax: {r.valid_syntax}")
print(f" has_mx: {r.has_mx}")
print(f" is_disposable: {r.is_disposable}")
print(f" is_catch_all: {r.is_catch_all}")
print(f" smtp_result: {r.smtp_result}")
Large lists (auto-batched)
Lists of any size are split into batches of 500 automatically:
ev = EmailValidator(api_key="your_api_key")
results = ev.validate(your_million_email_list) # sends 2000 API calls
Batch response with metadata
resp = ev.validate_batch(emails[:500]) # max 500 per call
print(resp.processed) # 500
print(resp.valid_count) # e.g. 412
print(resp.invalid_count) # e.g. 88
print(resp.duration_ms) # e.g. 1243
# Filter helpers
valid = resp.valid() # list[EmailResult] with score >= 60
invalid = resp.invalid() # list[EmailResult] with score < 60
Skip SMTP for faster results (layers 1–3 only)
# Global — all calls skip SMTP
ev = EmailValidator(api_key="your_api_key", skip_smtp=True)
# Per-call override
results = ev.validate(emails, skip_smtp=True)
Useful when you need instant syntax + MX + disposable checks and don't need SMTP confirmation.
Async client (FastAPI, asyncio)
import asyncio
from emailval import AsyncEmailValidator
async def main():
async with AsyncEmailValidator(api_key="your_api_key") as ev:
results = await ev.validate(["user@example.com"])
for r in results:
print(r.email, r.score)
asyncio.run(main())
FastAPI middleware example
from fastapi import FastAPI
from emailval import AsyncEmailValidator
app = FastAPI()
ev = AsyncEmailValidator(api_key="your_api_key")
@app.post("/signup")
async def signup(email: str):
results = await ev.validate([email])
if not results[0].is_valid:
return {"error": "Invalid email address"}
# ... continue signup
Error handling
from emailval import EmailValidator
from emailval.exceptions import AuthenticationError, RateLimitError, APIError
ev = EmailValidator(api_key="your_api_key")
try:
results = ev.validate(["user@example.com"])
except AuthenticationError:
print("Invalid API key — check emailval.net for your key")
except RateLimitError:
print("Slow down — too many requests")
except APIError as e:
print(f"API error {e.status_code}: {e}")
Benchmark
Validating 10,000 emails with SMTP (layers 1–5):
Total: ~45 seconds (20 concurrent batches of 500)
Cost: $8.00
ZeroBounce: $80.00 for the same list
Validating 10,000 emails without SMTP (layers 1–3, skip_smtp=True):
Total: ~3 seconds
Cost: $8.00 (same price — charged per email regardless)
API reference
EmailValidator(api_key, base_url, timeout, skip_smtp)
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
required | Your emailval API key |
base_url |
str |
https://api.emailval.net |
Override for testing |
timeout |
float |
60.0 |
Request timeout in seconds |
skip_smtp |
bool |
False |
Skip layers 4–5 globally |
ev.validate(emails, *, skip_smtp=None) → list[EmailResult]
Validate any number of emails. Large lists are split automatically.
ev.validate_batch(emails, *, skip_smtp=None) → ValidationResponse
Validate up to 500 emails. Returns metadata (duration, counts). Raises
BatchTooLargeError for lists > 500.
EmailResult fields
| Field | Type | Description |
|---|---|---|
email |
str |
The input email address |
score |
int |
Deliverability score 0–100 |
is_valid |
bool |
score >= 60 |
valid_syntax |
bool |
Passes RFC syntax check |
has_mx |
bool | None |
Domain has MX records |
is_disposable |
bool | None |
Known disposable provider |
is_catch_all |
bool | None |
Domain accepts all addresses |
smtp_result |
str | None |
accepted, rejected, inconclusive_*, skipped |
error |
str | None |
Processing error if any |
License
MIT. See LICENSE.
Links
- Website: emailval.net
- API docs: emailval.net/#api
- Issues: github.com/DmytroMosnenko/emailval-client/issues
Release files for emailval-client 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| emailval_client-1.0.0.tar.gz | 9.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| emailval_client-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.8 kB
Release files / emailval_client-1.0.0.tar.gz
| Download URL | emailval_client-1.0.0.tar.gz |
|---|---|
| Size | 9.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7af194395f0a5f16404d8f0e7031cbabb778c37cf38411e5b2e24d9a2244b038
|
|
BLAKE2b-256 checksum How to use checksums |
6d0a52ed544ff472be672a5d002ba4794f4a0d2b8dafc97c6cad3a108267e123
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency logRelease files / emailval_client-1.0.0-py3-none-any.whl
| Download URL | emailval_client-1.0.0-py3-none-any.whl |
|---|---|
| Size | 9.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3361ce460ad230b5969a88c5708a1f2f87fc0180294efc15e02aebba094695c2
|
|
BLAKE2b-256 checksum How to use checksums |
593f80a018878b606b832e3bf57f0de14d1b0ec96cc32dfb0f1b0b8482ed02d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency log