Python SDK
The official Python SDK for Receiver.email. Async, type-hinted with Pydantic models, and built for pytest and Python agents.
Installation
pip install receiverdotemailOr with poetry:
poetry add receiverdotemailConfiguration
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.
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.
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
inbox = await client.inboxes.create(prefix="signup") # prefix optional
inbox.address # "[email protected]"
inbox.prefix # "signup-k2m9qa"
inbox.created_at # datetime: pass it as sincewhoami(): Who the Key Belongs To
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.
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 attachmentswait_for_otp(): Wait for a Code
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 firstget_otp(): Check Without Waiting
otp = await client.emails.get_otp(recipient=inbox.address, since=inbox.created_at)
if otp is None:
... # No code yetTimeouts
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)
raiseTo 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
async for event in client.emails.stream(recipient=inbox.address):
print(event.email.subject, event.otp and event.otp.code)
if event.otp:
breaksend_test(): Send Yourself a Test Email
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
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:
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
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
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()raisesReceiverTimeoutErrorinstead of returningNone.sincereplacesreceived_afteron waits (the old name still works).project_idis optional with an API key.