Skip to main content

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:

  1. 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.

  2. When a human replies to that email, e2a matches the In-Reply-To header against stored Message-IDs and includes conversation_id in the webhook payload.

  3. For first-contact emails (no prior thread), conversation_id is None.

@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)SendResult
  • client.send(to, subject, body, content_type=None, conversation_id=None)SendResult
  • client.verify_webhook(body, signature)bool

verify_signature(body, signature, signing_key)bool

parse_payload(data, headers)WebhookPayload

Models

  • WebhookPayloadmessage_id, conversation_id, sender, recipient, raw_message, auth, received_at
  • AuthHeadersverified, sender, entity_type, domain_check, agent_id, human_id
  • SendResultstatus, message_id, method
  • E2AError — raised on API errors, has status_code and message

Project details


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.1.0.tar.gz (6.3 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.1.0-py3-none-any.whl (6.4 kB view details)

Uploaded Python 3

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

Hashes for e2a-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2fdf3fa9b68821add348d8ea24cc33391f392d38ca695a78a7a77a29acf87d16
MD5 fe0dca631c3a07b7ad2e46a7ed1428cf
BLAKE2b-256 aa240575dc4737f16aa4f7913f4ef0112a61d59f3edd53b944484e6525475fb1

See more details on using hashes here.

Provenance

The following attestation bundles were made for e2a-0.1.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.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

Hashes for e2a-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8f69c8fc966e2d90368233715389bbcc02ee1305a8a6f529cbfbd6848563ab41
MD5 3e12d9dfee20490d8a9bab592cfa41cb
BLAKE2b-256 acb572ac53a3dd7e77aaed30979a49f6ec81f80836f39c8a471a91c548c8b162

See more details on using hashes here.

Provenance

The following attestation bundles were made for e2a-0.1.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 Pingdom Monitoring Sentry Error logging StatusPage Status page