Metadata-Version: 2.4
Name: e2a
Version: 0.3.0
Summary: Python SDK for the e2a protocol — email-to-agent authentication
Project-URL: Homepage, https://e2a.dev
Project-URL: Repository, https://github.com/Mnexa-AI/e2a
Project-URL: Documentation, https://e2a.dev
Author-email: Mnexa AI <josh@mnexa.ai>
License-Expression: MIT
Keywords: agent,authentication,e2a,email,webhook
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Email
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pytest-httpx; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# e2a Python SDK

Python SDK for the [e2a protocol](https://e2a.dev) — email-to-agent authentication.

## Install

```bash
pip install e2a
```

## Quick start

```python
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:**
```python
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:**
```python
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.

```python
@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:

```python
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:

```python
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:

```python
@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:

```python
@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:

```python
# 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
- `SendResult` — `status`, `message_id`, `method`
- `AuthHeaders` — `verified`, `sender`, `entity_type`, `domain_check`, `agent_id`, `human_id`

### Exceptions

- `E2AError` — API error (has `status_code` and `message`)
- `WebhookVerificationError` — invalid or missing webhook signature
