Official Python SDK for the Pyzit disposable email detector API
Project description
██████╗ ██╗ ██╗███████╗██╗████████╗
██╔══██╗╚██╗ ██╔╝╚══███╔╝██║╚══██╔══╝
██████╔╝ ╚████╔╝ ███╔╝ ██║ ██║
██╔═══╝ ╚██╔╝ ███╔╝ ██║ ██║
██║ ██║ ███████╗██║ ██║
╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
████████╗███████╗███╗ ███╗██████╗ ███╗ ███╗ █████╗ ██╗██╗
╚══██╔══╝██╔════╝████╗ ████║██╔══██╗████╗ ████║██╔══██╗██║██║
██║ █████╗ ██╔████╔██║██████╔╝██╔████╔██║███████║██║██║
██║ ██╔══╝ ██║╚██╔╝██║██╔═══╝ ██║╚██╔╝██║██╔══██║██║██║
██║ ███████╗██║ ╚═╝ ██║██║ ██║ ╚═╝ ██║██║ ██║██║███████╗
╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝
The official Python SDK for the Pyzit Disposable Email Detector API
Stop fake signups. Block throwaway addresses. Protect your platform.
◈ What is this?
pyzit-tempmail is the official Python client for the Pyzit Disposable Email API — a service that detects throwaway, temporary, and fake email addresses in real time.
User submits email SDK validates it Your app decides
───────────────── ───────────────── ───────────────
user@mailnator.com ──────► is_disposable: True ──────► ✗ Reject signup
hi@yourcompany.com ──────► is_disposable: False ──────► ✓ Allow signup
Works with Django, Flask, FastAPI, plain scripts — sync and async both supported out of the box.
◈ Table of contents
- Installation
- Quick start
- API tiers
- All methods
- Async usage
- Response models
- Error handling
- Framework integrations
- Configuration
- Development
◈ Installation
pip install pyzit-tempmail
Or with uv (recommended):
uv add pyzit-tempmail
Requirements: Python 3.9 or higher.
◈ Quick start
from pyzit_tempmail import TempMailClient
client = TempMailClient("YOUR_API_TOKEN")
result = client.check("user@example.com")
if result.is_disposable:
print("❌ Disposable email — rejected")
else:
print("✅ Looks clean — allowed")
Get your API token at temp-mail-detector.pyzit.com.
Tip: Store your token in an environment variable, never hard-code it.
import os client = TempMailClient(os.environ["PYZIT_TOKEN"])
◈ API tiers
Three endpoints, three plan levels. Use only what you need.
┌─────────────────────────────────────────────────────────────────────┐
│ ENDPOINT OVERVIEW │
├──────────────────┬─────────────┬──────────────────────────────────┤
│ Method │ Plan │ What you get │
├──────────────────┼─────────────┼──────────────────────────────────┤
│ client.check() │ Free │ is_disposable + status │
│ client.detailed()│ Pro │ DNS, signals, risk score, reco │
│ client.bulk() │ Business │ Up to 100 emails, one request │
└──────────────────┴─────────────┴──────────────────────────────────┘
◈ All methods
CheckResult ← check(email: str)
The fastest check. One email in, one decision out. Free tier.
result = client.check("user@mailnator.com")
result.email # "user@mailnator.com"
result.is_disposable # True
result.status # "disposable"
result.is_clean # False (convenience property, opposite of is_disposable)
Response shape:
CheckResult
├── email str — the email you submitted
├── is_disposable bool — True = block it
├── status str — "clean" | "disposable"
└── is_clean bool — shorthand for not is_disposable
DetailedResult ← detailed(email: str)
Full forensic analysis. DNS records, SMTP probing, reputation scoring, domain age, signal breakdown. Pro tier.
result = client.detailed("suspicious@new-domain.io")
result.risk_level # "high"
result.recommendation # "reject"
result.reputation_score # 0.0
result.should_reject # True (convenience property)
# DNS intelligence
result.details.dns_intelligence.has_mx # False
result.details.dns_intelligence.has_spf # False
# Signal breakdown
result.details.signals.negative # ["no_mx_records", "new_domain", ...]
result.details.signals.positive # []
# Domain stability
result.details.stability.domain_age_days # 0
result.details.stability.is_new_domain # True
Response shape:
DetailedResult
├── email str
├── domain str
├── is_disposable bool
├── status str
├── reputation_score float — 0.0 (worst) to 1.0 (best)
├── risk_level str — "low" | "medium" | "high"
├── recommendation str — "accept" | "review" | "reject"
├── should_reject bool — shorthand: recommendation == "reject"
└── details
├── reputation
│ ├── reputation_score float
│ ├── is_disposable bool
│ ├── disposable_confidence float
│ ├── risk_level str
│ └── recommendation str
├── signals
│ ├── positive list[str] — trust signals
│ ├── negative list[str] — risk signals
│ └── neutral list[str] — ambiguous signals
├── dns_intelligence
│ ├── has_mx bool
│ ├── mx_records list[str]
│ ├── has_a_record bool
│ ├── has_spf bool
│ ├── has_dmarc bool
│ └── error str | None
└── stability
├── stability_score float
├── domain_age_days int
├── is_new_domain bool
└── risk_factors list[str]
BulkResult ← bulk(emails: list[str])
Validate up to 100 emails in a single API call. Business tier.
result = client.bulk([
"hi@pyzit.com",
"cyz@temp-mail.org",
"user@mailnator.com",
])
result.processed # 3
result.results # {"hi@pyzit.com": False, "cyz@temp-mail.org": True, ...}
result.disposable_emails() # ["cyz@temp-mail.org", "user@mailnator.com"]
result.clean_emails() # ["hi@pyzit.com"]
Response shape:
BulkResult
├── results dict[str, bool] — {email: is_disposable}
├── processed int — number of emails processed
├── disposable_emails() list[str] — emails where is_disposable=True
└── clean_emails() list[str] — emails where is_disposable=False
◈ Async usage
AsyncTempMailClient is a drop-in replacement for async codebases.
The method signatures are identical — just add await.
from pyzit_tempmail import AsyncTempMailClient
client = AsyncTempMailClient("YOUR_API_TOKEN")
# anywhere inside an async function:
result = await client.check("user@example.com")
result = await client.detailed("user@example.com")
result = await client.bulk(["a@x.com", "b@y.com"])
Both clients share the same response models, exceptions, and behaviour.
There is no functional difference other than async/await.
◈ Response models
All responses are fully typed Pydantic v2 models. You get autocomplete, runtime validation, and type safety for free.
pyzit_tempmail.models
│
├── CheckResult ← client.check()
├── DetailedResult ← client.detailed()
│ └── DetailedDetails
│ ├── ReputationDetail
│ ├── Signals
│ ├── DnsIntelligence
│ └── StabilityInfo
└── BulkResult ← client.bulk()
◈ Error handling
All SDK errors inherit from PyzitError, so you can catch everything
at one level or be specific.
PyzitError ← catch-all for any SDK error
├── AuthenticationError ← HTTP 401 — bad or missing token
├── PlanRequiredError ← HTTP 402/403 — need a higher plan
│ └── .required_plan ← str, e.g. "pro" or "business"
├── RateLimitError ← HTTP 429 — slow down
│ └── .retry_after ← int, seconds to wait
├── APIError ← any other non-2xx response
│ ├── .status_code ← int
│ └── .response_body ← str, raw body for debugging
└── TimeoutError ← request took too long
Recommended pattern — catch specific errors:
from pyzit_tempmail import (
TempMailClient,
AuthenticationError,
PlanRequiredError,
RateLimitError,
APIError,
TimeoutError,
PyzitError,
)
import time
client = TempMailClient("YOUR_TOKEN")
try:
result = client.check("user@example.com")
except AuthenticationError:
# token is wrong — fail fast, don't retry
raise SystemExit("Check your PYZIT_TOKEN environment variable.")
except PlanRequiredError as e:
# you called an endpoint above your plan
print(f"Upgrade to {e.required_plan} to use this feature.")
except RateLimitError as e:
# back off and retry
time.sleep(e.retry_after)
result = client.check("user@example.com")
except TimeoutError:
# API too slow — decide how to handle in your app
print("Pyzit API timed out — allowing email through.")
except APIError as e:
# unexpected server error
print(f"Unexpected error {e.status_code}: {e.response_body}")
except PyzitError:
# fallback for any other SDK error
print("Something went wrong with the Pyzit SDK.")
Minimal pattern — just block disposables, ignore errors gracefully:
def is_allowed(email: str) -> bool:
try:
return not client.check(email).is_disposable
except PyzitError:
return True # fail open — let the email through if API is down
◈ Framework integrations
Django
# validators.py
from django.core.exceptions import ValidationError
from pyzit_tempmail import TempMailClient, PyzitError
import os
_client = TempMailClient(os.environ["PYZIT_TOKEN"])
def validate_no_disposable_email(value: str) -> None:
try:
result = _client.check(value)
if result.is_disposable:
raise ValidationError(
"Disposable email addresses are not allowed. "
"Please use your real email."
)
except PyzitError:
pass # fail open if API is unreachable
# models.py
from django.db import models
from .validators import validate_no_disposable_email
class UserProfile(models.Model):
email = models.EmailField(validators=[validate_no_disposable_email])
FastAPI
from fastapi import FastAPI, HTTPException
from pyzit_tempmail import AsyncTempMailClient, PyzitError
import os
app = FastAPI()
client = AsyncTempMailClient(os.environ["PYZIT_TOKEN"])
@app.post("/register")
async def register(email: str):
try:
result = await client.check(email)
except PyzitError:
pass # fail open
else:
if result.is_disposable:
raise HTTPException(
status_code=422,
detail="Disposable email addresses are not permitted."
)
return {"status": "ok", "email": email}
Flask
from flask import Flask, request, jsonify
from pyzit_tempmail import TempMailClient, PyzitError
import os
app = Flask(__name__)
client = TempMailClient(os.environ["PYZIT_TOKEN"])
@app.route("/register", methods=["POST"])
def register():
email = request.json.get("email", "")
try:
result = client.check(email)
if result.is_disposable:
return jsonify({"error": "Disposable emails not allowed."}), 422
except PyzitError:
pass # fail open
return jsonify({"status": "ok"})
◈ Configuration
Both TempMailClient and AsyncTempMailClient accept the same options:
client = TempMailClient(
api_token = "YOUR_TOKEN", # required
timeout = 10.0, # seconds, default 10.0
base_url = "https://api-tempmail.pyzit.com/v1", # override for testing
)
| Parameter | Type | Default | Description |
|---|---|---|---|
api_token |
str |
— | Your Pyzit API token (required) |
timeout |
float |
10.0 |
Request timeout in seconds |
base_url |
str |
https://api-tempmail.pyzit.com/v1 |
Override for local testing / mocks |
◈ Development
This project is built with uv.
# clone
git clone https://github.com/pyzit/pyzit-tempmail-python.git
cd pyzit-tempmail-python
# install all dev dependencies
uv sync --extra dev
# run tests (no real API calls — all HTTP is mocked)
uv run pytest -v
# lint
uv run ruff check . --fix
uv run ruff format .
# type check (strict mypy)
uv run mypy src/
# build distribution
uv build
Project layout:
src/pyzit_tempmail/
├── __init__.py ← public API surface
├── exceptions.py ← all custom error classes
├── models.py ← pydantic response types
├── _http.py ← shared transport (headers, error handling)
├── client.py ← TempMailClient (sync)
└── async_client.py ← AsyncTempMailClient (async)
tests/
├── conftest.py ← shared fixtures
├── test_client.py ← sync client tests
├── test_async_client.py
└── test_models.py
◈ Changelog
See CHANGELOG.md.
◈ License
Built with care by the Pyzit team · https://pyzit.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tempmail_pyzit-0.1.0.tar.gz.
File metadata
- Download URL: tempmail_pyzit-0.1.0.tar.gz
- Upload date:
- Size: 14.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f216979fe335e62b68c5a9ebd157c5d403d291a9672ef516620e06a16023d47
|
|
| MD5 |
a184c3ef091f247db5270f04d2e18c30
|
|
| BLAKE2b-256 |
66d220ad1a3b4d547fe2212d6d5562cfbeaac18607001a084be350a659347cdf
|
File details
Details for the file tempmail_pyzit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tempmail_pyzit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8780c43b47a61a42320044e5fcfcc9c89ab5ce21570f7f37cca0f93703ead380
|
|
| MD5 |
28d26a28948fde5609611e0dc2696162
|
|
| BLAKE2b-256 |
467a71c9baf27649f2fef2ac61241d01afc8677fe973d7dd3180e86fea0aa5d0
|