Python SDK for the e2a protocol — email-to-agent authentication
Project description
e2a Python SDK
Async Python SDK for e2a — email for AI agents.
Install
pip install e2a # add the [ws] extra for client.listen(): pip install "e2a[ws]"
The SDK major version tracks the SDK package's own breaking changes and is
independent of the API version path (/v1): SDK 3.x targets the e2a v1 API.
Upgrading from 2.x to 3.0
3.0 is a breaking redesign. The SDK now wraps a generated /v1 client behind a
namespaced, async-only surface, with a typed error hierarchy, automatic
retries + idempotency, and async auto-pagination.
- Async-only, namespaced. The sync client and the flat methods are gone.
client.get_messages()→client.messages.list(address),client.get_message(id)→client.messages.get(address, id),client.send(...)→client.messages.send(address, body). Per-agent calls take an explicitaddress. - Webhook verification.
client.parse/client.parse_webhook/InboundEmailare removed. Verify and parse a delivery with the standaloneconstruct_event(raw_body, header, secret), which returns a typedWebhookEvent. Signatures are per-webhook (whsec_…), Stripe-style. - Typed errors. Failures raise
E2AErrorsubclasses (E2ANotFoundError,E2AConflictError,E2AValidationError,E2ARateLimitError, …) carrying.code,.status,.request_id, and.retryable.
Quick Start
import asyncio
from e2a.v1 import E2AClient
async def main():
# reads E2A_API_KEY; base_url defaults to https://api.e2a.dev
async with E2AClient() as client:
address = "my-agent@agents.e2a.dev"
# List endpoints return an AutoPager: async-iterate, or collect with a limit.
async for m in client.messages.list(address, status="unread"):
email = await client.messages.get(address, m.message_id)
print(email.subject)
await client.messages.reply(address, m.message_id, {"body": "Got it!"})
asyncio.run(main())
Send mail
await client.messages.send(address, {
"to": ["alice@example.com"],
"subject": "Hello",
"body": "Hi from my agent!",
"html_body": "<p>Hi!</p>",
})
The mail-sending writes (send / reply / forward / approve) auto-mint an
Idempotency-Key and reuse it across retries, so a network blip can't
double-send. Pass a stable key to also survive a process restart:
await client.messages.send(address, body, idempotency_key=derive_from(event))
Request bodies accept a plain dict (shown above) or the generated model
(from e2a.v1 import SendEmailRequest).
Verify a webhook
Each subscription is signed with its own whsec_… secret. construct_event
verifies the X-E2A-Signature header (replay-protected) and returns a typed
event. Pass the raw request body — re-serialized JSON won't match.
from e2a.v1 import construct_event, E2AWebhookSignatureError
@app.post("/webhook")
async def webhook(request):
try:
event = construct_event(await request.body(), request.headers["X-E2A-Signature"], SECRET)
except E2AWebhookSignatureError:
return Response(status_code=400)
if event.type == "email.received":
... # event.data carries the message payload
return {"ok": True}
During a rotation you can pass a list of secrets — accepted if any matches:
construct_event(body, header, [old_secret, new_secret]).
Resources
client.agents, client.messages, client.conversations, client.domains,
client.events, client.webhooks, client.account (with
client.account.suppressions), plus await client.info(). Each method maps to
a /v1 operation; per-agent methods take the agent address first.
E2AClient(api_key=None, *, base_url=None, max_retries=2, max_elapsed_ms=None)
api_key falls back to E2A_API_KEY; base_url to E2A_BASE_URL then
https://api.e2a.dev. Use it as an async context manager (or call
await client.aclose()) to close the underlying HTTP connections.
Errors
Every failure raises an E2AError (or subclass) with .code, .status,
.request_id, .retryable: E2AAuthError (401), E2APermissionError (403),
E2ANotFoundError (404), E2AConflictError (409), E2AValidationError (422),
E2AIdempotencyError, E2ARateLimitError (429), E2AServerError (5xx),
E2AConnectionError (no response), E2AWebhookSignatureError.
e2a hides the existence of agents you don't own —
agents.getof an unknown address raisesE2APermissionError(403), notE2ANotFoundError.
Pagination
List methods return an AutoPager — async-iterate it, or use
await pager.to_list(limit=N) (the limit is required, to bound memory) or
await pager.for_each(fn) (return False to stop early).
WebSocket (real-time delivery for local agents)
async for notif in client.listen("bot@agents.e2a.dev"): # falls back to E2A_AGENT_EMAIL
email = await client.messages.get(notif.recipient, notif.message_id)
client.listen(address) returns a WSStream (async-iterable of
WSNotification) that reconnects with exponential backoff. Requires the [ws]
extra (pip install "e2a[ws]").
License
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-3.0.0.tar.gz.
File metadata
- Download URL: e2a-3.0.0.tar.gz
- Upload date:
- Size: 174.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13f8d8af1942f4b7ceef5c1da120028f67b73398d3c417aee878820e03d4cfdc
|
|
| MD5 |
c404d10585ac6f7e66109314b344a605
|
|
| BLAKE2b-256 |
e81da8630575e3ff94a43cda3301b1d3c1eeb9d97ccdf1336014d90db877de31
|
Provenance
The following attestation bundles were made for e2a-3.0.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-3.0.0.tar.gz -
Subject digest:
13f8d8af1942f4b7ceef5c1da120028f67b73398d3c417aee878820e03d4cfdc - Sigstore transparency entry: 1945616021
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@65d02e6e0dc4fd92864a4a9bbbd65e0fe467603c -
Branch / Tag:
refs/tags/python-v3.0.0 - Owner: https://github.com/Mnexa-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@65d02e6e0dc4fd92864a4a9bbbd65e0fe467603c -
Trigger Event:
push
-
Statement type:
File details
Details for the file e2a-3.0.0-py3-none-any.whl.
File metadata
- Download URL: e2a-3.0.0-py3-none-any.whl
- Upload date:
- Size: 194.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
669f9889b3db5827d88d8d4cf46d0bec4b0302e8cbbe110632e16ebd500d953a
|
|
| MD5 |
eb6bf9f4e1afc1a6e88b8681a826ccb9
|
|
| BLAKE2b-256 |
2a9477e0c8c6688d92842aed509d295395007826337905ce9a90a30e9b5b152e
|
Provenance
The following attestation bundles were made for e2a-3.0.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-3.0.0-py3-none-any.whl -
Subject digest:
669f9889b3db5827d88d8d4cf46d0bec4b0302e8cbbe110632e16ebd500d953a - Sigstore transparency entry: 1945616058
- Sigstore integration time:
-
Permalink:
Mnexa-AI/e2a@65d02e6e0dc4fd92864a4a9bbbd65e0fe467603c -
Branch / Tag:
refs/tags/python-v3.0.0 - Owner: https://github.com/Mnexa-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@65d02e6e0dc4fd92864a4a9bbbd65e0fe467603c -
Trigger Event:
push
-
Statement type: