Metadata-Version: 2.4
Name: verifyanyemail
Version: 1.0.0
Summary: Official VerifyAnyEmail SDK for Python — real-time and bulk email verification.
Project-URL: Homepage, https://verifyany.email
Project-URL: Repository, https://github.com/verifyanyemail/verifyanyemail-python
Project-URL: Documentation, https://verifyany.email/docs
Author-email: VerifyAnyEmail <support@verifyany.email>
Maintainer-email: VerifyAnyEmail <support@verifyany.email>
License: MIT
License-File: LICENSE
Keywords: deliverability,email,email-validation,email-verification,mx,smtp,verifyanyemail
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# VerifyAnyEmail — Python SDK

Official Python SDK for the [VerifyAnyEmail](https://verifyany.email) API —
real-time and bulk email verification.

- **Zero third-party runtime dependencies** — stdlib only (`urllib`, `json`, `hmac`, `hashlib`).
- Python **3.8+**.
- Typed results (`VerificationResult`, `Checks`) with the raw JSON always available on `.raw`.
- Built-in webhook signature verification (constant-time).

> **Server-side use only.** Your API key can spend credits and read your
> account data — keep it secret. Never embed it in browser/mobile/client
> code. Load it from an environment variable or secrets manager.

## Install

```bash
pip install verifyanyemail
```

## Quickstart

### Verify a single address

```python
from verifyanyemail import VerifyAnyEmail

client = VerifyAnyEmail(api_key="vae_...")

result = client.verify("name@example.com")

print(result.status)        # deliverable | undeliverable | risky | unknown
print(result.score)         # 0..100
print(result.safe_to_send)  # bool
print(result.suggestion)    # "did you mean" correction, or None
print(result.checks.mailbox_exists)

# The full JSON payload is always available:
print(result.raw)
```

Options (both default to the server default of `True`):

```python
# DNS-only (skip the live SMTP probe), and skip catch-all detection:
result = client.verify(
    "name@example.com",
    smtp_probe=False,
    catch_all_detection=False,
)
```

### Test mode (free)

Any address at `@test.verifyany.email` returns a deterministic result
without charging a credit or probing a real mailbox:

```python
client.verify("deliverable@test.verifyany.email")   # -> deliverable
client.verify("undeliverable@test.verifyany.email") # -> undeliverable
client.verify("risky@test.verifyany.email")         # -> risky
client.verify("unknown@test.verifyany.email")       # -> unknown
```

### Batch: submit and poll

```python
import time
from verifyanyemail import VerifyAnyEmail

client = VerifyAnyEmail(api_key="vae_...")

submitted = client.verify_batch(
    emails=["a@example.com", "b@example.com"],
    name="newsletter-2026-07",
    webhook_url="https://your-app.example.com/webhooks/vae",  # optional
)
batch_id = submitted["batch"]["id"]

# Poll until finished
while True:
    status = client.get_batch(batch_id)["batch"]
    if status["status"] in ("completed", "failed"):
        break
    time.sleep(5)

# Page through results
page = 1
while True:
    resp = client.get_batch_results(batch_id, page=page, page_size=100)
    for row in resp["results"]:
        print(row["email"], row["status"], row["score"])
    if page * resp["pageSize"] >= resp["total"]:
        break
    page += 1
```

### Verify a webhook

When a batch completes, VerifyAnyEmail POSTs a signed `batch.completed`
event to your `webhookUrl` with the header:

```
X-VAE-Signature: sha256=<HMAC-SHA256(rawBody, yourWebhookSecret)>
```

Verify it against the **raw** request body (do not re-serialize the JSON):

```python
from verifyanyemail import verify_webhook_signature

# e.g. inside a Flask view
raw_body = request.get_data()                 # bytes
signature = request.headers.get("X-VAE-Signature", "")
secret = "whsec_..."                          # dashboard Settings

if not verify_webhook_signature(raw_body, signature, secret):
    abort(400)  # reject
# ...process the event, return 2xx to acknowledge
```

It is also available as `VerifyAnyEmail.verify_webhook_signature(...)`.

## Method reference

```python
VerifyAnyEmail(api_key, base_url="https://api.verifyany.email", timeout=30.0)
```

| Method | Returns | Notes |
| --- | --- | --- |
| `verify(email, smtp_probe=None, catch_all_detection=None)` | `VerificationResult` | Single address; unwraps `{ok, result}`. |
| `verify_batch(emails, name=None, webhook_url=None)` | `dict` | `{"ok", "batch": {"id", "status", "total"}}`. |
| `get_batch(batch_id)` | `dict` | `{"ok", "batch": {...}}` with status + summary. |
| `get_batch_results(batch_id, page=1, page_size=100)` | `dict` | `{"ok", "page", "pageSize", "total", "results": [...]}`. |
| `verify_webhook_signature(raw_body, signature_header, secret)` | `bool` | Static/module-level; constant-time. |

### `VerificationResult` fields

`email`, `normalized`, `account`, `domain`, `status`, `sub_status`,
`score`, `checks` (a `Checks`), `suggestion`, `safe_to_send`,
`duration_ms`, `checked_at`, and `raw` (the full JSON dict).

### `Checks` fields

`syntax`, `has_mx_records`, `null_mx`, `smtp_connectable`,
`mailbox_exists`, `catch_all`, `role_based`, `disposable`,
`free_provider`, `possible_typo`, `gibberish`, and `raw`. Fields the API
marks nullable may be `None` (e.g. when the SMTP probe was skipped).

## Error handling

Any non-2xx response raises `VerifyAnyEmailError`:

```python
from verifyanyemail import VerifyAnyEmail, VerifyAnyEmailError

client = VerifyAnyEmail(api_key="vae_...")

try:
    result = client.verify("name@example.com")
except VerifyAnyEmailError as exc:
    print(exc.status)   # HTTP status code
    print(exc.code)     # machine-readable code (JSON "error")
    print(exc.message)  # human-readable message
    print(exc.body)     # raw parsed JSON body (or None)
```

Common statuses:

| Status | Meaning |
| --- | --- |
| `400` | Invalid input |
| `401` | Missing or invalid API key |
| `402` | Insufficient credits |
| `403` | Feature not on your plan (batch) |
| `413` | Batch too large for your plan |
| `429` | Rate limited / concurrent batch limit reached |

Network/timeout failures raise `VerifyAnyEmailError` with `status == 0`
and `code == "network_error"`.

## License

MIT © 2026 VerifyAnyEmail
