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?

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

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

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

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.0.tar.gz (14.0 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.0-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

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

Hashes for tempmail_pyzit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1f216979fe335e62b68c5a9ebd157c5d403d291a9672ef516620e06a16023d47
MD5 a184c3ef091f247db5270f04d2e18c30
BLAKE2b-256 66d220ad1a3b4d547fe2212d6d5562cfbeaac18607001a084be350a659347cdf

See more details on using hashes here.

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

Hashes for tempmail_pyzit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8780c43b47a61a42320044e5fcfcc9c89ab5ce21570f7f37cca0f93703ead380
MD5 28d26a28948fde5609611e0dc2696162
BLAKE2b-256 467a71c9baf27649f2fef2ac61241d01afc8677fe973d7dd3180e86fea0aa5d0

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