e2a Python SDK
Python SDK for the e2a protocol — email-to-agent authentication.
Install
pip install e2a
Quick start
from e2a import E2AClient, InboundEmail
client = E2AClient(
api_key="e2a_your_api_key",
signing_key="e2a_your_signing_key",
)
@client.on_email
def handle(email: InboundEmail):
print(f"From: {email.sender}")
print(f"Subject: {email.subject}")
print(f"Body: {email.text_body}")
print(f"Verified: {email.is_verified}")
# Reply directly from the email object
email.reply("Thanks for reaching out!")
Mount the handler in your web framework:
FastAPI:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhook")
async def webhook(request: Request):
return client.handle_webhook(await request.body(), dict(request.headers))
Flask:
from flask import Flask, request
app = Flask(__name__)
@app.post("/webhook")
def webhook():
return client.handle_webhook(request.get_data(), dict(request.headers))
That's it — signature verification, payload parsing, and email content extraction are handled automatically.
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.
@client.on_email
def handle(email: InboundEmail):
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,
)
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"
InboundEmail
The InboundEmail object passed to your handler has these fields:
| Field | Type | Description |
|---|---|---|
message_id |
str |
Unique e2a message ID (used for replying) |
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 |
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)→SendResult
Async support
For async frameworks like FastAPI, use AsyncE2AClient. It has the same
interface but all I/O methods are async:
from e2a import AsyncE2AClient, AsyncInboundEmail
client = AsyncE2AClient(api_key="e2a_...", signing_key="e2a_...")
@client.on_email
async def handle(email: AsyncInboundEmail):
print(f"From: {email.sender}, Subject: {email.subject}")
await email.reply("Thanks!", conversation_id="conv_123")
@app.post("/webhook")
async def webhook(request: Request):
return await client.handle_webhook(await request.body(), dict(request.headers))
Or use receive() inline:
@app.post("/webhook")
async def webhook(request: Request):
email = client.receive(await request.body(), dict(request.headers))
await email.reply("Hello!")
return {"ok": True}
Using receive() directly
If you prefer not to use the @client.on_email decorator, you can parse
webhooks inline:
@app.post("/webhook")
async def webhook(request: Request):
email = client.receive(await request.body(), dict(request.headers))
# Use email.sender, email.subject, email.text_body, etc.
email.reply("Hello!")
return {"ok": True}
Low-level API
For full control, the client also exposes the underlying API methods:
# Reply to a message by ID
result = client.reply("msg_123", body="Hello!", html_body="<p>Hello!</p>")
# Send a new email
result = client.send(to="alice@example.com", subject="Hi", body="Hello")
# Verify a webhook signature manually
is_valid = client.verify_webhook(body_bytes, signature_string)
API Reference
E2AClient(api_key, signing_key, base_url="https://e2a.dev")
High-level:
@client.on_email— register an email handlerclient.handle_webhook(body, headers)→{"ok": True}client.receive(body, headers)→InboundEmail
Low-level:
client.reply(message_id, body, html_body=None, conversation_id=None)→SendResultclient.send(to, subject, body, content_type=None, conversation_id=None)→SendResultclient.verify_webhook(body, signature)→bool
Models
InboundEmail— parsed email with.reply()methodSendResult—status,message_id,methodAuthHeaders—verified,sender,entity_type,domain_check,agent_id,human_id
Exceptions
E2AError— API error (hasstatus_codeandmessage)WebhookVerificationError— invalid or missing webhook signature
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-0.3.0.tar.gz.
File metadata
- Download URL: e2a-0.3.0.tar.gz
- Upload date:
- Size: 10.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 |
3088b8f94c23ca63d7b5bb5dbd45ee22689b6e241ad3ef7717e8f884d19d50fd
|
|
| MD5 |
2a1239634df39e946749421f2d9eef00
|
|
| BLAKE2b-256 |
6ad636d8fb57699ac3fac04b1aeb28b99ff3596955119f77144435f90d615b2a
|
Provenance
The following attestation bundles were made for e2a-0.3.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-0.3.0.tar.gz -
Subject digest:
3088b8f94c23ca63d7b5bb5dbd45ee22689b6e241ad3ef7717e8f884d19d50fd - Sigstore transparency entry: 1168866935
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@cbe649f3b7e7dfa22ecf8661b98f8b0bbe3cfdb4 -
Branch / Tag:
refs/tags/python-v0.3.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@cbe649f3b7e7dfa22ecf8661b98f8b0bbe3cfdb4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file e2a-0.3.0-py3-none-any.whl.
File metadata
- Download URL: e2a-0.3.0-py3-none-any.whl
- Upload date:
- Size: 12.0 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 |
e2bd1e62b8d52a9af916399f56a35c2a059250eedb851936f80b9cc1b8a87f83
|
|
| MD5 |
571acf128801992f52a3fc824f2fe0a6
|
|
| BLAKE2b-256 |
7f5c77b08714318345a0191956b7a5bc4e2169d0572009c4f096c78b038df9d7
|
Provenance
The following attestation bundles were made for e2a-0.3.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-0.3.0-py3-none-any.whl -
Subject digest:
e2bd1e62b8d52a9af916399f56a35c2a059250eedb851936f80b9cc1b8a87f83 - Sigstore transparency entry: 1168867013
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@cbe649f3b7e7dfa22ecf8661b98f8b0bbe3cfdb4 -
Branch / Tag:
refs/tags/python-v0.3.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@cbe649f3b7e7dfa22ecf8661b98f8b0bbe3cfdb4 -
Trigger Event:
push
-
Statement type: