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
Quick start
Verify and parse incoming webhooks
from e2a import E2AClient, parse_payload
client = E2AClient(
api_key="e2a_your_api_key",
signing_key="e2a_your_signing_key",
)
# In your webhook handler (Flask, FastAPI, etc.)
def handle_webhook(request):
body = request.get_data()
signature = request.headers["X-E2A-Webhook-Signature"]
if not client.verify_webhook(body, signature):
return "invalid signature", 401
payload = parse_payload(request.json, dict(request.headers))
print(f"From: {payload.sender}")
print(f"To: {payload.recipient}")
print(f"Verified: {payload.auth.verified}")
print(f"Message ID: {payload.message_id}")
Reply to an email
# Use the message_id from the webhook payload
result = client.reply(
message_id=payload.message_id,
body="Thanks for your email!",
html_body="<p>Thanks for your email!</p>", # optional
)
print(f"Sent via {result.method}, ID: {result.message_id}")
Send a new email
result = client.send(
to="alice@example.com",
subject="Hello from my agent",
body="This is a message from an AI agent.",
)
Conversation threading
e2a supports an opaque conversation_id that lets your agent track multi-turn
email threads. This is useful when your agent system has its own concept of
conversations and needs to route follow-up emails to the right one.
How it works:
-
When your agent replies or sends an email, pass a
conversation_id. e2a stores a mapping from the outgoing email's Message-ID to your conversation ID. -
When a human replies to that email, e2a matches the
In-Reply-Toheader against stored Message-IDs and includesconversation_idin the webhook payload. -
For first-contact emails (no prior thread),
conversation_idisNone.
@app.post("/webhook")
async def webhook(request: Request):
body = await request.body()
signature = request.headers.get("X-E2A-Webhook-Signature", "")
if not e2a.verify_webhook(body, signature):
raise HTTPException(401, "invalid signature")
payload = parse_payload(await request.json(), dict(request.headers))
if payload.conversation_id:
# This is a follow-up — route to the existing conversation
conversation = get_conversation(payload.conversation_id)
else:
# First contact — create a new conversation
conversation = create_conversation(sender=payload.sender)
response = conversation.generate_reply(payload)
# Tag the reply with your conversation ID so future emails are linked
e2a.reply(
payload.message_id,
body=response.text,
html_body=response.html,
conversation_id=conversation.id,
)
return {"ok": True}
The same works for client.send() — pass conversation_id when initiating an
outbound email, and any reply to it will arrive with that ID in the webhook.
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"
FastAPI example
from fastapi import FastAPI, Request, HTTPException
from e2a import E2AClient, parse_payload
app = FastAPI()
e2a = E2AClient(api_key="e2a_...", signing_key="e2a_...")
@app.post("/webhook")
async def webhook(request: Request):
body = await request.body()
signature = request.headers.get("X-E2A-Webhook-Signature", "")
if not e2a.verify_webhook(body, signature):
raise HTTPException(401, "invalid signature")
data = await request.json()
payload = parse_payload(data, dict(request.headers))
# Process the email...
response = process_email(payload)
# Reply
if payload.message_id:
e2a.reply(payload.message_id, body=response)
return {"ok": True}
API Reference
E2AClient(api_key, signing_key, base_url="https://e2a.dev")
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
verify_signature(body, signature, signing_key) → bool
parse_payload(data, headers) → WebhookPayload
Models
WebhookPayload—message_id,conversation_id,sender,recipient,raw_message,auth,received_atAuthHeaders—verified,sender,entity_type,domain_check,agent_id,human_idSendResult—status,message_id,methodE2AError— raised on API errors, 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-0.1.0.tar.gz.
File metadata
- Download URL: e2a-0.1.0.tar.gz
- Upload date:
- Size: 6.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2fdf3fa9b68821add348d8ea24cc33391f392d38ca695a78a7a77a29acf87d16
|
|
| MD5 |
fe0dca631c3a07b7ad2e46a7ed1428cf
|
|
| BLAKE2b-256 |
aa240575dc4737f16aa4f7913f4ef0112a61d59f3edd53b944484e6525475fb1
|
Provenance
The following attestation bundles were made for e2a-0.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-0.1.0.tar.gz -
Subject digest:
2fdf3fa9b68821add348d8ea24cc33391f392d38ca695a78a7a77a29acf87d16 - Sigstore transparency entry: 1168698121
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@1150bd9f15135b2960d1c8df8a8197fb9a8dd845 -
Branch / Tag:
refs/tags/python-v0.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@1150bd9f15135b2960d1c8df8a8197fb9a8dd845 -
Trigger Event:
push
-
Statement type:
File details
Details for the file e2a-0.1.0-py3-none-any.whl.
File metadata
- Download URL: e2a-0.1.0-py3-none-any.whl
- Upload date:
- Size: 6.4 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 |
8f69c8fc966e2d90368233715389bbcc02ee1305a8a6f529cbfbd6848563ab41
|
|
| MD5 |
3e12d9dfee20490d8a9bab592cfa41cb
|
|
| BLAKE2b-256 |
acb572ac53a3dd7e77aaed30979a49f6ec81f80836f39c8a471a91c548c8b162
|
Provenance
The following attestation bundles were made for e2a-0.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-0.1.0-py3-none-any.whl -
Subject digest:
8f69c8fc966e2d90368233715389bbcc02ee1305a8a6f529cbfbd6848563ab41 - Sigstore transparency entry: 1168698378
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@1150bd9f15135b2960d1c8df8a8197fb9a8dd845 -
Branch / Tag:
refs/tags/python-v0.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@1150bd9f15135b2960d1c8df8a8197fb9a8dd845 -
Trigger Event:
push
-
Statement type: