Skip to content

Latest commit

 

History

History
323 lines (226 loc) · 9.79 KB

python.mdx

File metadata and controls

323 lines (226 loc) · 9.79 KB
title Python SDK
description ZeroDrop Python SDK reference — email.otp and email.magic_link auto-extracted. No Docker, no regex, no signup.

Python SDK

Full API reference for zerodrop — the ZeroDrop Python SDK.

Installation

pip install zerodrop

No dependencies. Python 3.8+. On Python 3.8 to 3.10, use zerodrop 0.3.0 or later: earlier versions raise a ValueError on every email, because they can't parse its timestamp there.

ZeroDrop class

from zerodrop import ZeroDrop

mail = ZeroDrop(api_key=None, base_url="https://zerodrop.dev")

Parameters

Parameter Type Description
api_key str (optional) Free API key from zerodrop.dev/keys. Defaults to the ZERODROP_API_KEY environment variable.
base_url str (optional) Custom base URL for self-hosted instances

Without an API key, anyone who knows an inbox's name can read it, one sender can reach only 5 different inboxes a day (past that, the inbox gets a notice from notice@zerodrop.dev instead of the email), and each inbox expires 30 minutes after its latest email, on a shared domain. With a free key (zerodrop 0.2.0 or later), generate_inbox() starts names with your key's prefix, only your key can read those inboxes, and mail to them isn't limited. mail.prefix holds the prefix ("k7x2"), or None without a key.


generate_inbox()

mail.generate_inbox() -> str

Generates a unique inbox address instantly. No network request. With a key, the name starts with your prefix.

inbox = mail.generate_inbox()
# "swift-x7k2m@zerodrop-sandbox.online" without a key
# "k7x2-swift-x7k2m@zerodrop-sandbox.online" with the key zd_k7x2_…

wait_for_latest()

mail.wait_for_latest(
    inbox: str,
    timeout: int = 10000,
    poll_interval: float = 2.0,
    filter_: Optional[ZeroDropFilter] = None
) -> ZeroDropEmail

Polls until an email arrives in the inbox. Raises ZeroDropTimeoutError if no email arrives within the timeout, and ZeroDropHeldError at once if ZeroDrop held the email (see Error types).

Parameters

Parameter Type Default Description
inbox str required Inbox address to poll
timeout int 10000 Milliseconds to wait
poll_interval float 2.0 Seconds between polls
filter_ ZeroDropFilter None Optional filter to match specific emails
# Default — 10s timeout
email = mail.wait_for_latest(inbox)

# Custom timeout
email = mail.wait_for_latest(inbox, timeout=30000)

# With filter
email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter(has_otp=True))

fetch_latest()

mail.fetch_latest(
    inbox: str,
    filter_: Optional[ZeroDropFilter] = None
) -> Optional[ZeroDropEmail]

Fetches the latest email without waiting. Returns None if the inbox is empty or no email matches the filter. Raises ZeroDropHeldError if nothing matches and the inbox holds a notice that ZeroDrop held an email.

email = mail.fetch_latest(inbox)
if email:
    print(email.subject)

Email Filtering

Filter emails by sender, subject, body, or extracted fields. Useful when multiple emails land in the same inbox.

from zerodrop import ZeroDrop, ZeroDropFilter

mail = ZeroDrop()

# Only match emails from a specific sender
email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter(
    from_="noreply@yourapp.com"
))

# Only match emails with a specific subject
email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter(
    subject="Verify your email"
))

# Only match emails that contain an OTP
email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter(
    has_otp=True
))

# Only match emails that contain a magic link
email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter(
    has_magic_link=True
))

# Combine multiple filters
email = mail.wait_for_latest(inbox, timeout=15000, filter_=ZeroDropFilter(
    from_="noreply@yourapp.com",
    subject="Reset",
    has_magic_link=True
))

All string filters are case-insensitive partial matches.


ZeroDropEmail type

@dataclass
class ZeroDropEmail:
    id: str                    # Unique message ID
    from_: str                 # Sender address
    to: str                    # Recipient address
    subject: str               # Email subject
    body: str                  # Parsed plain-text body
    raw_body: str              # Full raw MIME message
    received_at: datetime      # UTC timestamp
    otp: Optional[str]         # Auto-extracted OTP code (4-8 digits)
    magic_link: Optional[str]  # Auto-extracted verification/reset link

otp

4-8 digit numeric code extracted from the email body. Detected near labels like code, otp, pin, verification. None if not detected.

email = mail.wait_for_latest(inbox)
print(email.otp)  # "123456" or None

magic_link

Verification or reset URL extracted from the email body. Detected for URLs containing verify, confirm, reset, token, activate, or auth. None if not detected.

print(email.magic_link)  # "https://app.com/verify?token=abc" or None

ZeroDropFilter type

@dataclass
class ZeroDropFilter:
    from_: Optional[str] = None           # Partial match on sender address
    subject: Optional[str] = None         # Partial match on subject line
    body: Optional[str] = None            # Partial match on email body
    has_otp: Optional[bool] = None        # Only match emails with an OTP
    has_magic_link: Optional[bool] = None # Only match emails with a magic link

Error types

ZeroDropTimeoutError

Raised when no email arrives within the timeout.

from zerodrop import ZeroDrop, ZeroDropTimeoutError

try:
    email = mail.wait_for_latest(inbox, timeout=10000)
except ZeroDropTimeoutError:
    print("No email received in time")

ZeroDropHeldError

Raised when ZeroDrop held the email. Without an API key, one sender can reach only 5 different inboxes a day. Past that, the email isn't stored, and the inbox gets a notice from notice@zerodrop.dev instead. wait_for_latest() raises at once rather than waiting for the timeout, and the message is the server's reason. A free key from zerodrop.dev/keys lifts the limit for inboxes under your key's prefix.

from zerodrop import ZeroDropHeldError

try:
    email = mail.wait_for_latest(inbox)
except ZeroDropHeldError as e:
    print(e)  # why, and how to get a free key

ZeroDropHeldError is new in zerodrop 0.3.0. Older versions treat the notice as an email: without a filter, wait_for_latest returns it (its otp is None), and a filter such as ZeroDropFilter(has_otp=True) skips it, so the wait ends in ZeroDropTimeoutError.

ZeroDropNetworkError

Raised when the API is unreachable. Includes a link to the status page.

from zerodrop import ZeroDropNetworkError

try:
    email = mail.fetch_latest(inbox)
except ZeroDropNetworkError as e:
    print(f"Network error — check https://zerodrop.instatus.com")

ZeroDropAuthError

Raised on a 401: the inbox belongs to an account's prefix and the request had no key or the wrong one. The message carries the server's reason.


pytest example

import re
from playwright.sync_api import expect
from zerodrop import ZeroDrop

mail = ZeroDrop()

def test_signup_email_verification(page):
    inbox = mail.generate_inbox()

    page.goto("/signup")
    page.fill('[name="email"]', inbox)
    page.fill('[name="password"]', "TestPassword123!")
    page.click('[type="submit"]')

    # Wait for verification email
    email = mail.wait_for_latest(inbox, timeout=15000)

    # OTP auto-extracted — no regex needed
    assert email.otp is not None
    page.fill('[name="otp"]', email.otp)
    page.click('[type="submit"]')

    expect(page).to_have_url(re.compile(r".*/dashboard"))


def test_magic_link_login(page):
    inbox = mail.generate_inbox()

    page.goto("/login")
    page.fill('[name="email"]', inbox)
    page.click('button:has-text("Send magic link")')

    email = mail.wait_for_latest(inbox, timeout=15000)

    # magic_link auto-extracted — no HTML parsing needed
    assert email.magic_link is not None
    page.goto(email.magic_link)

    expect(page).to_have_url(re.compile(r".*/dashboard"))

Parallel test runs

generate_inbox() runs locally — no network request, no throttling. Without an API key, though, one sender can reach only 5 different inboxes a day; with a free key, inboxes under your prefix aren't counted:

# Safe to run in parallel — generate_inbox() is local
inboxes = [mail.generate_inbox() for _ in range(50)]

Free vs Workspace

Free Workspace
Inbox generation ✓ ✓
OTP auto-extraction ✓ ✓
Magic link extraction ✓ ✓
Email filtering ✓ ✓
Email retention 30 min Extended
Custom domains ✗ ✓
API key ✓ free ✓

Get a Workspace at zerodrop.dev/pricing


Zero telemetry

The SDK does not send analytics, usage metrics, or environment data to any server. The only network requests made are explicit inbox polls to zerodrop.dev/api/inbox/{name}.

Your CI pipeline is your business.