Integration
Python SDK
Fully typed Python client for all Dead Simple Email endpoints. Sync and async, with framework integrations built in.
Installation
pip install deadsimple-email
Quick Start
Create a client, make an inbox, and send your first email in four lines:
from deadsimple import DeadSimple client = DeadSimple(api_key="dse_your_api_key") inbox = client.inboxes.create(display_name="My Agent") client.messages.send( inbox_id=inbox.inbox_id, to="user@example.com", subject="Hello from my agent", text_body="This email was sent by an AI agent.", )
Async Client
For async frameworks like FastAPI, use the async client. Every method from the sync client has an async equivalent:
from deadsimple import AsyncDeadSimple client = AsyncDeadSimple(api_key="dse_your_api_key") inbox = await client.inboxes.create(display_name="Async Agent") messages = await client.messages.list(inbox_id=inbox.inbox_id)
Resources
Every API endpoint is available as a typed method on the client:
| Resource | Methods |
|---|---|
client.inboxes |
create, list, get, delete, bulk_create, bulk_delete |
client.messages |
send, list, get, reply, forward |
client.threads |
list, get |
client.webhooks |
create, list, get, update, delete (with inbox_ids scope) |
client.api_keys |
create (with inbox_ids scope), list, get, delete |
client.calendar |
create, list, list_all, get, update, delete, invite, rsvp, feed, rotate_feed |
client.metrics |
get |
client.domains |
add, list, verify, delete |
client.identity |
sign_in, enroll_key, list_keys, delete_key, connections |
client.usage |
get |
Identity: sign an agent in to other apps
Any app that offers Sign in with Dead Simple accepts an inbox as a login over standard OpenID Connect. Hand the SDK the app's authorization URL and it completes the flow with the API key the client already holds:
# authorization_url is the app's "Sign in with Dead Simple" link # (its /authorize URL with PKCE, state and scopes already set). result = client.identity.sign_in(inbox.inbox_id, authorization_url) print(result["redirect_to"], result["final_url"], result["status"]) # follow=False returns redirect_to only, so you can drive the HTTP client yourself. # Agents that should not hold a full API key: enrol a P-256 public key once, # then sign a short-lived ES256 assertion at sign-in time. key = client.identity.enroll_key(inbox.inbox_id, public_key=open("agent.pub.pem").read(), name="worker-7") print(key["kid"]) client.identity.list_keys(inbox.inbox_id) # shows last_used_at per key client.identity.delete_key(inbox.inbox_id, key["kid"]) # Every app the inbox has signed in to, with scope and sign-in counts client.identity.connections(inbox.inbox_id)
Pay per request. The SDK needs an API key, but the API itself does not: priced routes answer 402 with no credentials and accept USDC over x402 or a Stripe card over MPP inline, and the first paid inbox returns an API key you can then pass to DeadSimple(api_key=...). Prices, headers and an httpx-based x402 client are on the pay per request page.
Calendar: events, invitations, RSVPs and a feed
Every inbox has a calendar. Create events, send them as real iCalendar invitations, answer invitations that arrived by email, and hand a human the subscription URL. Times need a zone: a trailing Z, an offset, or timezone= next to a local time.
# Create, then invite. Nothing is sent until invite. event = client.calendar.create(inbox.inbox_id, title="Kickoff", start="2026-09-22T11:00:00", timezone="America/New_York", duration_minutes=30, attendees=["dana@example.com"]) sent = client.calendar.invite(inbox.inbox_id, event["event_id"], text_body="Agenda to follow.") # Reschedule: update raises sequence, invite again sends a revision, not a duplicate client.calendar.update(inbox.inbox_id, event["event_id"], start="2026-09-23T11:00:00", timezone="America/New_York") client.calendar.invite(inbox.inbox_id, event["event_id"]) # Invitations that arrived by email are already on the calendar (source == "inbound") for ev in client.calendar.list(inbox.inbox_id, status="confirmed")["events"]: if ev["source"] == "inbound": client.calendar.rsvp(inbox.inbox_id, ev["event_id"], "accepted", comment="See you then.") # Subscription URLs for Google Calendar, Apple Calendar or Outlook; rotate if the URL leaks feed = client.calendar.feed(inbox.inbox_id) # feed["feed_url"], feed["webcal_url"] client.calendar.rotate_feed(inbox.inbox_id) # Every event on every inbox the key can see client.calendar.list_all(start_from="2026-09-21", start_to="2026-09-27")
Metrics: a time series for the account or an inbox
client.metrics.get() wraps GET /v1/metrics. Each bucket carries sent, received, bounced, complained, opened, clicked, webhook_deliveries, webhook_failures and injection_flagged, plus totals over the window. Windows span at most 92 days; hourly buckets are available for windows of seven days or less.
# Last 30 days, daily buckets (the defaults) m = client.metrics.get() print(m.totals.sent, m.totals.received, m.totals.bounced) # One week, hourly, for two inboxes m = client.metrics.get(from_="2026-09-10", to="2026-09-17", interval="hour", inbox_ids=[support_id, billing_id]) for bucket in m.series: if bucket.webhook_failures or bucket.injection_flagged: print(bucket.start, bucket.webhook_failures, bucket.injection_flagged)
Inbox-scoped API keys and webhooks
Pass inbox_ids when creating an API key and the key can see and act on those inboxes only: any other inbox of the account answers 404, account-wide listings (inboxes, messages, threads, metrics) are filtered to them, and the key cannot create inboxes or mint a key with more reach than it has. Webhooks take the same field and fire only for events on those inboxes.
# A key for one agent, limited to its own inbox key = client.api_keys.create("support-agent", permissions=["read", "write"], inbox_ids=[support_id]) print(key.key) # shown once # A webhook that only hears that inbox hook = client.webhooks.create("https://agents.example.com/hooks/support", events=["message.received", "calendar.event.created"], inbox_ids=[support_id]) # Change the scope later; inbox_ids=[] makes it account-wide again client.webhooks.update(hook.webhook_id, inbox_ids=[support_id, billing_id]) client.webhooks.get(hook.webhook_id)
Framework Integrations
Install optional extras to get pre-built tool definitions for popular AI agent frameworks:
# LangChain toolkit pip install deadsimple-email[langchain] # CrewAI tools pip install deadsimple-email[crewai] # OpenAI Agents SDK pip install deadsimple-email[openai-agents] # MCP server pip install deadsimple-email[mcp] # All framework integrations pip install deadsimple-email[all]
Each extra adds framework-specific tool definitions. See the individual integration pages for details:
Bulk Operations
Create or delete many inboxes in a single API call:
# Create 10 inboxes at once inboxes = client.inboxes.bulk_create( count=10, display_name_prefix="Agent", ) for inbox in inboxes: print(f"{inbox.email} ready") # Clean up when done ids = [inbox.inbox_id for inbox in inboxes] client.inboxes.bulk_delete(inbox_ids=ids)
CLI Included
The Python SDK includes the dse command-line tool. Once installed, you can manage inboxes, send email, and check usage from your terminal:
export DSE_API_KEY=dse_your_api_key dse inboxes list dse send --inbox inb_xxx --to user@example.com --subject "Hello" --body "Hi there" dse usage
See the full CLI documentation for all available commands.
Configuration
The client accepts the API key directly, or reads it from the DSE_API_KEY environment variable:
import os from deadsimple import DeadSimple # Explicit key client = DeadSimple(api_key="dse_your_api_key") # Or from environment (reads DSE_API_KEY automatically) client = DeadSimple()