Python SDK

The official Python SDK for Receiver.email. Async, type-hinted with Pydantic models, and built for pytest and Python agents.

Installation

bash
pip install receiverdotemail

Or with poetry:

bash
poetry add receiverdotemail

Configuration

Use the client as an async context manager so its connections are closed. With no arguments it reads RECEIVER_API_KEY and RECEIVER_API_URL from the environment.

Python
from receiverdotemail import ReceiverClient

async with ReceiverClient() as client:
    ...

# Or pass everything explicitly
client = ReceiverClient(
    api_key="recv_live_...",
    base_url="https://api.receiver.email/api/v1",  # Optional
    timeout=30,                                     # Optional: per-request seconds
    user_agent="my-tool/1.0",                       # Optional
)

With an API key, project_id is optional on every email and inbox method: the client calls whoami() once and remembers the key's project.

The Recommended Pattern

Mint an address, use it, then wait from its created_at, so mail from before the address existed can never match.

Python
inbox = await client.inboxes.create(prefix="signup")
await app.sign_up(inbox.address)

otp = await client.emails.wait_for_otp(
    recipient=inbox.address,
    since=inbox.created_at,
    timeout=60,
)
await app.verify(otp.code)

Inboxes and Identity

inboxes.create(): Mint an Address

Python
inbox = await client.inboxes.create(prefix="signup")  # prefix optional
inbox.address     # "[email protected]"
inbox.prefix      # "signup-k2m9qa"
inbox.created_at  # datetime: pass it as since

whoami(): Who the Key Belongs To

Python
me = await client.whoami()
print(me.project.name, me.project.email_suffix)  # "Signup tests", "[email protected]"

Waiting for Email

wait_for(): Long-Poll for an Email

Returns the first email matching the filters received after since. timeout is in seconds, 30 by default, and can be any length: the SDK repeats 60 second server waits until it runs out, then raises ReceiverTimeoutError. It never returns None.

Python
email = await client.emails.wait_for(
    recipient=inbox.address,          # Optional filters
    sender="[email protected]",
    subject="Reset",                  # Substring, case-insensitive
    since=inbox.created_at,           # Default: the last 60 seconds
    timeout=60,                       # Seconds (default 30)
)

print(email.subject)
print(email.links)
print(email.text_body)                # get() adds HTML, headers and attachments

wait_for_otp(): Wait for a Code

Python
otp = await client.emails.wait_for_otp(
    recipient=inbox.address,
    since=inbox.created_at,
    code_length=6,                    # Optional: 4, 6 or 8
    timeout=60,
)

otp.code         # "847291"
otp.source       # "subject" or "body"
otp.email        # The email it came from
otp.candidates   # Every code found, newest first

get_otp(): Check Without Waiting

Python
otp = await client.emails.get_otp(recipient=inbox.address, since=inbox.created_at)
if otp is None:
    ...  # No code yet

Timeouts

Python
from receiverdotemail import ReceiverTimeoutError

try:
    await client.emails.wait_for(recipient=inbox.address, since=inbox.created_at, timeout=30)
except ReceiverTimeoutError as e:
    print(e.since, e.waited_seconds)
    raise

To wait for the next email on an address, pass the received_at of the one you already have as since.

stream(): Emails as They Arrive

Python
async for event in client.emails.stream(recipient=inbox.address):
    print(event.email.subject, event.otp and event.otp.code)
    if event.otp:
        break

send_test(): Send Yourself a Test Email

Python
inbox = await client.inboxes.create()
await client.emails.send_test(to=inbox.address)  # 10 per project per hour
otp = await client.emails.wait_for_otp(recipient=inbox.address, since=inbox.created_at)

Reading Email

Python
page = await client.emails.list(recipient=inbox.address, limit=50)
if page.has_more:
    next_page = await client.emails.list(cursor=page.next_cursor)

async for email in client.emails.list_all(sender="[email protected]"):
    print(email.subject)

email = await client.emails.get("email_xyz789")
print(email.html_body, email.headers, email.attachments)
await client.emails.delete("email_xyz789")

# Convenience finders
await client.emails.find_by_recipient(inbox.address)
await client.emails.find_by_sender("[email protected]")
await client.emails.find_by_subject("Password Reset")

Webhooks

Managing webhooks needs a dashboard access token, not an API key (see the API reference). Verifying deliveries works anywhere:

Python
from receiverdotemail import verify_webhook_signature

ok = verify_webhook_signature(
    secret=os.environ["RECEIVER_WEBHOOK_SECRET"],  # whsec_...
    header=request.headers.get("Receiver-Signature"),
    body=await request.body(),                      # The raw request body
)

# Managing webhooks: a client made with a dashboard access token
async with ReceiverClient(api_key=access_token) as admin:
    hook = await admin.webhooks.create(project_id="proj_abc123", url="https://example.com/hooks/receiver")
    await admin.webhooks.list("proj_abc123")
    await admin.webhooks.test("proj_abc123", hook.id)
    await admin.webhooks.deliveries("proj_abc123", hook.id)
    await admin.webhooks.delete("proj_abc123", hook.id)

Projects, Organizations and API Keys

Python
project = await client.projects.get("proj_abc123")
print(project.email_suffix)  # "[email protected]"

# These need a dashboard access token
await client.projects.list("org_abc123")
await client.projects.create(organization_id="org_abc123", name="CI Tests")
await client.organizations.list()

key = await client.api_keys.create(project_id="proj_abc123", name="CI Pipeline")
print(key.key)  # Full key, only shown once
await client.api_keys.revoke("proj_abc123", key.id)

Error Handling

Python
from receiverdotemail import (
    ReceiverError,
    ReceiverTimeoutError,
    RateLimitError,
    AuthenticationError,
    NotFoundError,
)

try:
    otp = await client.emails.wait_for_otp(recipient=address, since=since)
except ReceiverTimeoutError:
    ...  # Nothing arrived in time
except RateLimitError as e:
    print(f"Retry at {e.reset_at}")
except AuthenticationError:
    ...  # Bad or revoked key
except NotFoundError:
    ...  # Wrong project or email id
except ReceiverError as e:
    print(e.status_code, e.code, e.message)

Upgrading to 2.0

  • wait_for() raises ReceiverTimeoutError instead of returning None.
  • since replaces received_after on waits (the old name still works).
  • project_id is optional with an API key.