Skip to main content

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 handler
  • client.handle_webhook(body, headers){"ok": True}
  • client.receive(body, headers)InboundEmail

Low-level:

  • client.reply(message_id, body, html_body=None, conversation_id=None)SendResult
  • client.send(to, subject, body, content_type=None, conversation_id=None)SendResult
  • client.verify_webhook(body, signature)bool

Models

  • InboundEmail — parsed email with .reply() method
  • SendResultstatus, message_id, method
  • AuthHeadersverified, sender, entity_type, domain_check, agent_id, human_id

Exceptions

  • E2AError — API error (has status_code and message)
  • 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

e2a-0.3.0.tar.gz (10.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

e2a-0.3.0-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

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

Hashes for e2a-0.3.0.tar.gz
Algorithm Hash digest
SHA256 3088b8f94c23ca63d7b5bb5dbd45ee22689b6e241ad3ef7717e8f884d19d50fd
MD5 2a1239634df39e946749421f2d9eef00
BLAKE2b-256 6ad636d8fb57699ac3fac04b1aeb28b99ff3596955119f77144435f90d615b2a

See more details on using hashes here.

Provenance

The following attestation bundles were made for e2a-0.3.0.tar.gz:

Publisher: publish-sdk.yml on Mnexa-AI/e2a

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for e2a-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e2bd1e62b8d52a9af916399f56a35c2a059250eedb851936f80b9cc1b8a87f83
MD5 571acf128801992f52a3fc824f2fe0a6
BLAKE2b-256 7f5c77b08714318345a0191956b7a5bc4e2169d0572009c4f096c78b038df9d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for e2a-0.3.0-py3-none-any.whl:

Publisher: publish-sdk.yml on Mnexa-AI/e2a

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page