Skip to main content

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_verified and entra_gct_error from VerificationResult. They were undocumented diagnostics that never changed a verdict; the score and reason already carry what they said.
  • Added is_locked to VerificationResult and BulkJobResult: 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)

Source distribution for verifications-email 0.3.0
File Size Uploaded Provenance
verifications_email-0.3.0.tar.gz 14.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for verifications-email 0.3.0
File Interpreter ABI Platform
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.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page