Skip to main content

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.

PyPI version Python License mypy uv


◈ What is this?

tempmail-pyzit 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

pip install tempmail-pyzit

Or with uv (recommended):

uv add tempmail-pyzit

Requirements: Python 3.9 or higher.


◈ Quick start

from tempmail_pyzit 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

CheckResultcheck(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

DetailedResultdetailed(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]

BulkResultbulk(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 tempmail_pyzit 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.

tempmail_pyzit.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 tempmail_pyzit 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 tempmail_pyzit 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 tempmail_pyzit 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 tempmail_pyzit 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-org/temp-mail-detector-sdk-python.git
cd temp-mail-detector-sdk-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/tempmail_pyzit/
├── __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

MIT © Pyzit


Built with care by the Pyzit team · https://pyzit.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tempmail_pyzit-0.1.2.tar.gz (14.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tempmail_pyzit-0.1.2-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

Details for the file tempmail_pyzit-0.1.2.tar.gz.

File metadata

  • Download URL: tempmail_pyzit-0.1.2.tar.gz
  • Upload date:
  • Size: 14.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for tempmail_pyzit-0.1.2.tar.gz
Algorithm Hash digest
SHA256 3621e2ad077ba938bb539c35d3d744c5f44b449f387b3fab0b2c3b54cbc7ad11
MD5 2f766e1ca7f090b62e27446ee42e6bbc
BLAKE2b-256 f7f5342d3cd7b3130da06661beec7536f41cebd0516816540c6275d86f968a72

See more details on using hashes here.

File details

Details for the file tempmail_pyzit-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: tempmail_pyzit-0.1.2-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

Hashes for tempmail_pyzit-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0a9ee6260c6f681ab6962a533be9dfa9f20d09d1c85b89e963700cf500137752
MD5 ca9cfe4879798817926f8ca24c938f2b
BLAKE2b-256 894c1c5e4a5a802ec3571c039e881870ae4252ee00734d18f7315f944287e3cb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page