verifications-email
Official Python SDK for the EVA Email Verification API.
Single dependency (httpx). Works with Python 3.9+. Fully typed (py.typed).
Installation
pip install verifications-email
Quick Start
from eva_email import EvaClient
eva = EvaClient(api_key="eva_...")
result = eva.verify("user@example.com")
print(result.data.score) # 85
print(result.data.risk) # "safe"
print(result.data.smtp_check) # "deliverable"
print(result.data.is_disposable) # False
print(result.rate_limit.remaining) # 199
Features
- Single email verification
- Batch verification (up to 100 emails)
- Async bulk file upload (CSV/XLSX) with polling
- Domain deliverability reports (SPF/DKIM/DMARC/blacklists)
- Webhook signature verification (HMAC-SHA256)
- Auto-retry with exponential backoff on 429/5xx
- Rate limit info on every response
- Full type annotations (PEP 561)
API Reference
Constructor
eva = EvaClient(
api_key="eva_...", # Required
base_url="https://verifications.email", # Default
timeout=30.0, # Seconds (default: 30)
max_retries=3, # Retries on 429/5xx (default: 3)
retry_delay=1000.0, # Base retry delay in ms (default: 1000)
)
Context Manager
with EvaClient(api_key="eva_...") as eva:
result = eva.verify("user@example.com")
# Client is automatically closed
Single Verification
result = eva.verify("user@gmail.com")
print(result.data.email) # "user@gmail.com"
print(result.data.score) # 0-100
print(result.data.risk) # "safe" | "risky" | "catchall" | "invalid" | "unknown"
print(result.data.smtp_check) # "deliverable" | "undeliverable" | "risky" | "unknown"
print(result.data.is_disposable) # False
print(result.data.is_role_account) # False
print(result.data.is_catch_all) # False
print(result.data.is_free_provider) # True
print(result.data.spam_trap_risk) # "low" | "medium" | "high"
print(result.data.suggested_correction) # None or "gmail.com"
print(result.data.mx_records) # ["aspmx.l.google.com", ...]
Batch Verification
result = eva.verify_batch([
"user1@gmail.com",
"user2@company.com",
"fake@disposable.xyz",
])
print(result.data.total) # 3
for r in result.data.results:
print(r.email, r.risk, r.score)
Bulk File Upload + Polling
# Upload the file
with open("emails.csv", "rb") as f:
job = eva.upload_bulk(f, webhook_url="https://myapp.com/webhook/eva")
print(job.data.id) # "abc-123"
print(job.data.status) # "pending"
# Poll until complete
completed = eva.wait_for_bulk_job(
job.data.id,
interval=3.0, # poll every 3s (default: 5s)
timeout=300.0, # max wait 5min (default: 10min)
on_progress=lambda j: print(f"{j.completed_count}/{j.total_count} processed"),
)
# Download results as typed objects
results = eva.get_bulk_job_results(completed.data.id)
for r in results.data:
print(r.email, r.risk, r.score)
# Or download as CSV string
csv_data = eva.get_bulk_job_results(completed.data.id, format="csv")
with open("results.csv", "w") as f:
f.write(csv_data)
Domain Deliverability
result = eva.get_domain_deliverability("example.com")
print(result.data.score) # 85
print(result.data.grade) # "A"
print(result.data.authentication.spf.found) # True
print(result.data.authentication.dkim.found) # True
print(result.data.authentication.dmarc.policy) # "reject"
print(result.data.blacklist.listed) # False
Bulk Job Management
# List all jobs
jobs = eva.list_bulk_jobs()
# Get specific job status
job = eva.get_bulk_job("job-id-123")
Webhook Verification
Verify incoming webhook signatures without instantiating the client:
from eva_email import verify_webhook_signature, parse_webhook_event
# Option 1: Verify only
is_valid = verify_webhook_signature(
payload=raw_body,
signature=headers["X-EVA-Signature"],
secret=os.environ["EVA_WEBHOOK_SECRET"],
)
# Option 2: Verify + parse in one step
event = parse_webhook_event(
payload=raw_body,
signature=headers["X-EVA-Signature"],
secret=os.environ["EVA_WEBHOOK_SECRET"],
)
print(event["job_id"])
print(event["summary"]) # {"safe": 800, "risky": 100, "catchall": 50, "invalid": 50, "unknown": 12}
Flask Example
from flask import Flask, request
from eva_email import parse_webhook_event, WebhookSignatureError
app = Flask(__name__)
@app.post("/webhook/eva")
def handle_webhook():
try:
event = parse_webhook_event(
payload=request.get_data(as_text=True),
signature=request.headers["X-EVA-Signature"],
secret=os.environ["EVA_WEBHOOK_SECRET"],
)
print(f"Job {event['job_id']} completed:", event["summary"])
return "", 200
except WebhookSignatureError:
return "Invalid signature", 401
Error Handling
All API errors are raised as typed exceptions:
from eva_email import (
EvaClient,
EvaError,
RateLimitError,
QuotaExceededError,
AuthenticationError,
NotFoundError,
)
eva = EvaClient(api_key="eva_...")
try:
result = eva.verify("test@example.com")
except RateLimitError as e:
# Per-minute rate limit hit — wait and retry
print(f"Rate limited. Retry after {e.retry_after}s")
print(f"Remaining: {e.rate_limit.remaining}")
except QuotaExceededError:
# Monthly quota exhausted, no PAYG credits left
print("Quota exceeded. Purchase credit packs to continue.")
except AuthenticationError:
# Invalid or missing API key
print("Check your API key")
except NotFoundError:
# Resource not found (e.g., invalid bulk job ID)
print("Not found")
except EvaError as e:
# Other API error
print(e.message, e.code, e.status)
Rate Limit Info
Every response includes rate limit data:
result = eva.verify("user@example.com")
print(result.rate_limit.limit) # 200 (per-minute limit)
print(result.rate_limit.remaining) # 199
print(result.rate_limit.reset) # datetime object (UTC)
print(result.rate_limit.credits_remaining) # None or int (PAYG credits)
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
api_key |
str |
— | Required. Your EVA API key. |
base_url |
str |
https://verifications.email |
API base URL. |
timeout |
float |
30.0 |
Request timeout (seconds). |
max_retries |
int |
3 |
Max retries on 429/5xx errors. |
retry_delay |
float |
1000.0 |
Base delay (ms) for exponential backoff. |
Requirements
- Python >= 3.9
- httpx >= 0.27
Changes
0.3.0
- Removed
yahoo_api_verified,yahoo_api_error,entra_gct_verifiedandentra_gct_errorfromVerificationResult. They were undocumented diagnostics that never changed a verdict; the score andreasonalready carry what they said. - Added
is_lockedtoVerificationResultandBulkJobResult: the provider reported the account locked (temporary or permanent lockout).
License
MIT
Metadata
Release files for verifications-email 0.3.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 | Provenance | |
|---|---|---|---|---|
| verifications_email-0.3.0.tar.gz | 14.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| verifications_email-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.1 kB
Release files / verifications_email-0.3.0.tar.gz
| Download URL | verifications_email-0.3.0.tar.gz |
|---|---|
| Size | 14.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5e6027f6905f32679ce2ab357f3ae8b95970cf9e5a8a099ad64f6acf5a20b2ee
|
|
BLAKE2b-256 checksum How to use checksums |
005a935bccb6aed8eee8b59b6afab8ac19654ee2aa7aae76fa08a411e115af2f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / verifications_email-0.3.0-py3-none-any.whl
| Download URL | verifications_email-0.3.0-py3-none-any.whl |
|---|---|
| Size | 14.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
594a74dc2f8797bf05e993f853d0ec8c610b03e033beb5cadbbbff56cfccf162
|
|
BLAKE2b-256 checksum How to use checksums |
7af3781d758ebe4c07554db9c2829240b97be87e08e3b3da3b6eb4ec85f22d89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Security for verifications-email 0.3.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.