Dead Simple Email — Python SDK
The official Python SDK for Dead Simple Email, the email API for AI agents.
- Typed responses — dataclass models with IDE autocompletion, not raw dicts
- Sync + async —
DeadSimplefor synchronous code,AsyncDeadSimplefor async/await - Idempotency — pass
idempotency_keyto any create/send method for safe retries - Webhook verification — HMAC-SHA256 signature validation built in
- Full API coverage — inboxes, messages, threads, webhooks, domains, API keys, workspaces, usage, attachments
Install
pip install deadsimple-email
Quick Start
from deadsimple import DeadSimple
client = DeadSimple("dse_your_api_key")
# Create an inbox
inbox = client.inboxes.create(display_name="Support Bot")
print(f"Inbox: {inbox.email}")
# Send an email
result = client.messages.send(
inbox_id=inbox.inbox_id,
to="user@example.com",
subject="Hello from my AI agent",
text_body="This email was sent by an AI agent using Dead Simple Email.",
)
print(f"Sent: {result.message_id}")
# Read received messages
messages = client.messages.list(inbox_id=inbox.inbox_id)
for msg in messages.messages:
print(f" {msg.from_email}: {msg.subject}")
# Reply to a message
client.messages.reply(
inbox_id=inbox.inbox_id,
message_id=messages.messages[0].message_id,
text_body="Thanks for your email!",
)
# List conversation threads
threads = client.threads.list(inbox_id=inbox.inbox_id)
for t in threads.threads:
print(f" Thread: {t.subject} ({t.message_count} messages)")
# Register a webhook for real-time notifications
webhook = client.webhooks.create(
url="https://your-app.com/webhook",
events=["message.received"],
# Optional: headers your endpoint requires, sent on every attempt and retry
headers={"Authorization": "Bearer your-endpoint-token"},
)
print(f"Webhook secret: {webhook.signing_secret}")
# Rotate or clear those headers later
client.webhooks.set_headers(webhook.webhook_id, {"Authorization": "Bearer rotated"})
Agent Self-Onboarding (OTP / magic links)
Give an agent a real inbox and it can sign itself up for other services — receive the confirmation email, pull the code or link, and finish the flow with no human and no MIME parsing. Every inbound email is also scanned for prompt injection, so the agent knows what's safe to act on.
from datetime import datetime, timezone
inbox = client.inboxes.create(display_name="Signup Bot")
# 1. Kick off the signup on the target service using inbox.email ...
# (fill the form / call their API with inbox.email)
# 2. Wait for the verification email and get the code in one call.
# `since` ignores any older code already sitting in the inbox.
started = datetime.now(timezone.utc).isoformat()
result = client.inboxes.wait_for_verification(
inbox.inbox_id,
from_contains="stripe.com", # optional: only this sender
since=started,
timeout=90,
)
if result:
print("Code:", result["verification_code"]) # e.g. "482913"
print("Link:", result["magic_link_url"]) # magic link, if any
# 3. Submit the code / open the link to finish signing up.
get_verification() is the non-blocking version — it returns immediately with
found=False if nothing has arrived yet, so you can poll on your own schedule.
Both wrap GET /v1/inboxes/{id}/verification, which works from any language.
Async Usage
from deadsimple import AsyncDeadSimple
async with AsyncDeadSimple("dse_your_api_key") as client:
inbox = await client.inboxes.create(display_name="Async Bot")
await client.messages.send(
inbox_id=inbox.inbox_id,
to="user@example.com",
subject="Hello from async",
text_body="Sent asynchronously.",
)
Bulk Operations
# Create 50 inboxes at once
result = client.inboxes.bulk_create([
{"display_name": f"Agent {i}", "tags": ["batch-1"]}
for i in range(50)
])
print(f"Created {result.created}, failed {result.failed}")
Custom Domains
# Add your domain
domain = client.domains.add("mail.yourcompany.com")
# Shows DNS records to configure
for record in domain.dns_records:
print(f" {record['type']} {record['name']} -> {record['value']}")
# Check verification
status = client.domains.verify(domain.domain_id)
print(f"Status: {status.status}")
Multi-Tenant Workspaces
# Create an isolated namespace for a customer
workspace = client.workspaces.create(name="customer-acme", description="Acme Corp")
print(f"Workspace API key: {workspace.api_key['key']}")
# Use the workspace's scoped API key for isolated access
acme_client = DeadSimple(workspace.api_key["key"])
acme_inbox = acme_client.inboxes.create(display_name="Acme Support")
Idempotent Requests
import uuid
# Safe to retry — same key = same result, no duplicates
key = str(uuid.uuid4())
inbox = client.inboxes.create(display_name="Bot", idempotency_key=key)
inbox_again = client.inboxes.create(display_name="Bot", idempotency_key=key) # Returns same inbox
Webhook Signature Verification
from deadsimple.webhooks import verify_signature
# In your webhook handler (e.g., Flask, FastAPI):
try:
verify_signature(
payload=request.body,
signature=request.headers["X-DSE-Signature"],
secret="whsec_your_signing_secret",
)
# Signature valid — process the event
except Exception:
# Signature invalid — reject the request
return Response(status_code=401)
Usage Metrics
usage = client.usage.get()
print(f"Plan: {usage.plan_name}")
print(f"Inboxes: {usage.inboxes['used']} / {usage.inboxes['limit']}")
print(f"Emails this month: {usage.emails['sent_this_month']}")
Error Handling
from deadsimple import DeadSimple, RateLimitError, NotFoundError, ValidationError
import time
client = DeadSimple("dse_your_api_key")
try:
inbox = client.inboxes.get("nonexistent")
except NotFoundError:
print("Inbox not found")
except RateLimitError as e:
print(f"Rate limited, retry in {e.retry_after}s")
time.sleep(e.retry_after)
except ValidationError as e:
print(f"Bad request: {e.message}")
for detail in e.details:
print(f" {detail['field']}: {detail['message']}")
All Resources
| Resource | Methods |
|---|---|
client.inboxes |
create, bulk_create, list, get, update, delete |
client.messages |
send, list, get, reply, reply_all, forward |
client.threads |
list, get |
client.webhooks |
create, list, delete |
client.domains |
add, list, verify, delete |
client.api_keys |
create, list, delete |
client.workspaces |
create, list, get, update, delete |
client.usage |
get |
client.attachments |
get_url |
Pricing
| Plan | Price | Inboxes | Emails/mo |
|---|---|---|---|
| Free | $0 | 5 | 5,000 |
| Hobby | $5/mo | 15 | 15,000 |
| Pro | $29/mo | 100 | 100,000 |
| Scale | $99/mo | 500 | 500,000 |
Webhook signing included on all plans (competitors charge $200/mo).
License
MIT
Metadata
Release files for deadsimple-email 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| deadsimple_email-0.9.0.tar.gz | 51.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| deadsimple_email-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 97.7 kB
Release files / deadsimple_email-0.9.0.tar.gz
| Download URL | deadsimple_email-0.9.0.tar.gz |
|---|---|
| Size | 51.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
42e4e80159dea7000363dc0a259c142957b90b7bffeeef586b302bec72258c7f
|
|
BLAKE2b-256 checksum How to use checksums |
5a4a0effd1deb084e979a136a9fc94de4a6a8c06652d67e0c0b81cebe084016c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Release files / deadsimple_email-0.9.0-py3-none-any.whl
| Download URL | deadsimple_email-0.9.0-py3-none-any.whl |
|---|---|
| Size | 46.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
17312243d81ec89572448edfc7e29036c522b31f66cc91a642b32321f7b32e24
|
|
BLAKE2b-256 checksum How to use checksums |
23955f2e733dbb48ad52aa8a65817a3b6c34f3ce14f8042ed7513cffa93412a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Security for deadsimple-email 0.9.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.