Python SDK for the e2a protocol — email-to-agent authentication
Project description
e2a Python SDK
Python SDK for the e2a protocol — email-to-agent authentication.
Install
pip install e2a
For WebSocket real-time delivery:
pip install e2a[ws]
Import paths
The stable, pinned API surface lives under e2a.v1:
from e2a.v1 import E2AClient, AsyncE2AClient, E2AApi
Top-level e2a imports remain available as convenience aliases to the current stable version, but use e2a.v1 in examples, production code, and version-pinned integrations.
Quick start
from e2a.v1 import E2AClient
# Reads E2A_API_KEY from environment automatically
client = E2AClient()
# Or pass explicitly:
# client = E2AClient(api_key="e2a_your_api_key")
Mount the webhook in your web framework:
FastAPI:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhook")
async def webhook(request: Request):
email = client.parse(await request.body())
print(f"From: {email.sender}, Subject: {email.subject}")
email.reply("Thanks for reaching out!")
return {"ok": True}
Flask:
from flask import Flask, request
app = Flask(__name__)
@app.post("/webhook")
def webhook():
email = client.parse(request.get_data())
email.reply("Thanks for reaching out!")
return {"ok": True}
Raw vs high-level API
The SDK has two layers:
E2AApi/AsyncE2AApi— raw typed HTTP client. Returns generated Pydantic models. Uses/api/v1/paths.E2AClient/AsyncE2AClient— high-level wrapper. Returns parsedInboundEmailobjects with.reply().
Access the raw layer through client.api:
from e2a.v1 import E2AClient
client = E2AClient(api_key="e2a_...")
# High-level: returns InboundEmail with parsed MIME, .reply(), etc.
email = client.get_message("msg_123")
# Raw: returns generated MessageDetail Pydantic model
detail = client.api.get_message("bot@agents.e2a.dev", "msg_123")
Conversation threading
e2a supports an opaque conversation_id that lets your agent track multi-turn
email threads. Pass it when replying, and e2a will include it in the webhook
when the human responds.
@app.post("/webhook")
async def webhook(request: Request):
email = client.parse(await request.body())
if email.conversation_id:
# Follow-up — route to existing conversation
conversation = get_conversation(email.conversation_id)
else:
# First contact — create a new conversation
conversation = create_conversation(sender=email.sender)
response = conversation.generate_reply(email)
# Tag the reply so future emails in this thread are linked
email.reply(
body=response.text,
html_body=response.html,
conversation_id=conversation.id,
)
return {"ok": True}
Works the same for outbound emails:
result = client.send(
to="alice@example.com",
subject="Following up",
body="Hi Alice, just checking in.",
conversation_id="conv_abc123",
)
# When Alice replies, the webhook will include conversation_id="conv_abc123"
Attachments
Receiving attachments
Inbound email attachments are automatically parsed and available on
email.attachments:
email = client.parse(body)
for att in email.attachments:
print(f"{att.filename} ({att.content_type}, {att.size} bytes)")
save_file(att.filename, att.data)
Sending attachments
Pass Attachment objects when sending or replying:
from e2a.v1 import Attachment
# Read a file
with open("report.pdf", "rb") as f:
pdf_data = f.read()
# Send with attachment
client.send(
to="alice@example.com",
subject="Your report",
body="See attached.",
attachments=[
Attachment(
filename="report.pdf",
content_type="application/pdf",
data=pdf_data,
size=len(pdf_data),
)
],
)
# Or reply with attachment
email.reply(
"Here's the file you requested.",
attachments=[
Attachment(filename="data.csv", content_type="text/csv", data=csv_bytes, size=len(csv_bytes))
],
)
Async support
For async frameworks like FastAPI, use AsyncE2AClient. Same interface,
all I/O methods are async:
from e2a.v1 import AsyncE2AClient
client = AsyncE2AClient() # reads E2A_API_KEY from env
@app.post("/webhook")
async def webhook(request: Request):
email = client.parse(await request.body())
await email.reply("Thanks!", conversation_id="conv_123")
return {"ok": True}
WebSocket (real-time delivery for local agents)
Local-mode agents can receive emails in real time via WebSocket using the
async listen() method. No public URL needed.
pip install e2a[ws]
import asyncio
from e2a.v1 import AsyncE2AClient
async def main():
async with AsyncE2AClient(api_key="e2a_...") as client:
async for email in client.listen("my-bot@agents.e2a.dev"):
print(f"From: {email.sender}, Subject: {email.subject}")
await email.reply("Got it!")
asyncio.run(main())
listen() connects to e2a's WebSocket endpoint, receives lightweight
notifications, fetches the full message via REST, and yields
AsyncInboundEmail objects. It reconnects automatically with exponential
backoff (1s, 2s, 4s, ... up to 30s).
The WebSocket protocol is notification-only (server-to-client). The client never sends application frames.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
agent_email |
str |
client.agent_email |
Agent email to listen for |
reconnect |
bool |
True |
Auto-reconnect on disconnect |
max_backoff |
float |
30.0 |
Maximum reconnect delay (seconds) |
Agent and domain management
from e2a.v1 import E2AClient
client = E2AClient(api_key="e2a_...")
# Register a local-mode agent
result = client.register_agent("my-bot")
print(result.email) # my-bot@agents.e2a.dev
# Custom domain agent
result = client.register_agent(email="support@mycompany.com", agent_mode="cloud", webhook_url="https://mycompany.com/webhook")
# List agents
agents = client.list_agents()
# Domain management
client.register_domain("mycompany.com")
client.verify_domain("mycompany.com")
client.list_domains()
client.delete_domain("mycompany.com")
Sending emails
Send outbound emails directly:
result = client.send(
to="alice@example.com",
subject="Hello from my agent",
body="Hi Alice!",
conversation_id="conv_abc123", # optional
)
print(result.status, result.message_id)
InboundEmail
| Field | Type | Description |
|---|---|---|
message_id |
str |
Unique e2a message ID |
conversation_id |
str | None |
Your thread ID from a prior reply, or None for first contact |
sender |
str |
Sender email address |
recipient |
str |
Recipient email address (your agent) |
subject |
str |
Email subject line |
text_body |
str |
Plain-text email body |
html_body |
str | None |
HTML email body, if present |
attachments |
list[Attachment] |
File attachments (empty list if none) |
received_at |
str | None |
Timestamp when the message was received |
is_verified |
bool |
Whether the sender's identity is verified |
auth |
AuthHeaders |
Full authentication details |
raw_message |
bytes |
Raw RFC 2822 email bytes |
Methods:
email.reply(body, html_body=None, conversation_id=None, attachments=None)→SendResult
API Reference
E2AClient(api_key=None, agent_email=None, base_url="https://e2a.dev")
High-level sync client. api_key falls back to E2A_API_KEY env var.
client.parse(body)→InboundEmail— accepts bytes, str, dict, orMessageDetailclient.get_message(message_id)→InboundEmailclient.get_messages(status="unread", page_size=50)→MessageListclient.reply(message_id, body, ...)→SendResultclient.send(to, subject, body, ...)→SendResultclient.api→E2AApi(raw typed access)
AsyncE2AClient(api_key=None, agent_email=None, base_url="https://e2a.dev")
Same as E2AClient — all I/O methods are async. parse() is sync (no I/O needed).
client.listen(agent_email=None, reconnect=True, max_backoff=30.0)→AsyncIterator[AsyncInboundEmail](requirese2a[ws])client.api→AsyncE2AApi(raw typed async access)
Models
InboundEmail/AsyncInboundEmail— parsed email with.reply()Attachment—filename,content_type,data(bytes),sizeSendResult—status,message_id,methodAuthHeaders—verified,sender,entity_type,domain_check,delegation,signature,timestamp
Exceptions
E2AApiError— API error (hasstatus_codeandmessage)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file e2a-1.1.0.tar.gz.
File metadata
- Download URL: e2a-1.1.0.tar.gz
- Upload date:
- Size: 66.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
526f033ddd6b717f8f0fad93e71b92718d5a11c122e8bb4e20cdb96c78430903
|
|
| MD5 |
a98eab698982288fe4a614eed1b81b6c
|
|
| BLAKE2b-256 |
691ef9921c88f01de41d8d1dce8d94de25656e595d20fd260cab589e9efa7a33
|
Provenance
The following attestation bundles were made for e2a-1.1.0.tar.gz:
Publisher:
publish-sdk.yml on Mnexa-AI/e2a
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
e2a-1.1.0.tar.gz -
Subject digest:
526f033ddd6b717f8f0fad93e71b92718d5a11c122e8bb4e20cdb96c78430903 - Sigstore transparency entry: 1238403023
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@e4c2b5003033567ae2c66d561b46edc4e8fe2965 -
Branch / Tag:
refs/tags/python-v1.1.0 - Owner: https://github.com/Mnexa-AI
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@e4c2b5003033567ae2c66d561b46edc4e8fe2965 -
Trigger Event:
push
-
Statement type:
File details
Details for the file e2a-1.1.0-py3-none-any.whl.
File metadata
- Download URL: e2a-1.1.0-py3-none-any.whl
- Upload date:
- Size: 18.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5a1933fa4deef7ee38dc2c0020c88fdd95eafad76ad516c5e97d129bbd672ba
|
|
| MD5 |
aa685c6bd5084509e68afd655fe88e2c
|
|
| BLAKE2b-256 |
edc2c67f1fdd01922dda0c47e4548c5377d939497253f450e6bea2e49b90732e
|
Provenance
The following attestation bundles were made for e2a-1.1.0-py3-none-any.whl:
Publisher:
publish-sdk.yml on Mnexa-AI/e2a
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
e2a-1.1.0-py3-none-any.whl -
Subject digest:
f5a1933fa4deef7ee38dc2c0020c88fdd95eafad76ad516c5e97d129bbd672ba - Sigstore transparency entry: 1238403040
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@e4c2b5003033567ae2c66d561b46edc4e8fe2965 -
Branch / Tag:
refs/tags/python-v1.1.0 - Owner: https://github.com/Mnexa-AI
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@e4c2b5003033567ae2c66d561b46edc4e8fe2965 -
Trigger Event:
push
-
Statement type: