Metadata-Version: 2.4
Name: ujex-postbox
Version: 0.2.0
Summary: Official Python SDK for Ujex Postbox — email-for-AI-agents with built-in prompt-injection scoring, auth verdicts, and plus-addressed task threading.
Project-URL: Homepage, https://ujex.dev
Project-URL: Documentation, https://ujex.dev/docs/sdks/python
Author-email: Akshay Sarode <akshay@akshaysarode.com>
License: Apache-2.0
License-File: LICENSE
Keywords: agents,ai,email,postbox,ujex
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Provides-Extra: crewai
Requires-Dist: crewai>=0.55; extra == 'crewai'
Requires-Dist: pydantic>=2; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
Requires-Dist: pydantic>=2; extra == 'langchain'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.11; extra == 'llamaindex'
Description-Content-Type: text/markdown

# ujex-postbox

> The Python SDK for **Ujex Postbox** — email for AI agents.

```bash
pip install ujex-postbox
```

## 30-second demo

```python
from ujex_postbox import PostboxClient, plus_address

client = PostboxClient(device_key="ap_live_...", agent_id="agent-hello")

# Send
resp = client.send(
    to=["alice@vendor.com"],
    subject="Re: invoice",
    body="On it — sending by Friday.",
    session_id="sess_run_20260618_001",
    require_human=True,
)
print(resp.id, resp.status, resp.quota_remaining)

# Plus-address a per-task inbox (no new inbox needed)
addr = plus_address("agent-hello@in.ujex.dev", task_id="proj-42-abc")
# -> agent-hello+proj-42-abc@in.ujex.dev

# Read an inbound message
m = client.read_message(agent_id="agent-hello", message_id="msg_01...")
print(m.pi_score, m.pi_reasons, m.auth_verdict.dkim, m.plus_task_id)
```

## Why not AgentMail / SES / SendGrid?

Postbox surfaces Ujex's agent-specific signals as first-class fields:

| Field | What it gives you |
|---|---|
| `m.pi_score` / `m.pi_reasons` | Gemini-scored prompt-injection risk on every inbound, 0-1. |
| `m.auth_verdict.dkim/spf/dmarc` | RFC 5321/7208/7489 verdicts — bool per message, no parsing SPF strings yourself. |
| `m.plus_task_id` | Task id automatically extracted from the plus-address. |
| `resp.quota_remaining` | Per-agent monthly budget remaining in USD-equivalent units. |
| `require_human=True` on send | Returns `waiting_approval`; the outbound message dispatches after app/dashboard approval. |

## API

| Method | Purpose |
|---|---|
| `PostboxClient.send(to, subject, body, session_id, thread_key?, idempotency_key?, require_human?)` | Send an email. Returns `SendResult`. |
| `PostboxClient.list_messages(agent_id, direction?, limit?, since?)` | List inbound or outbound messages. Returns `list[Message]`. |
| `PostboxClient.read_message(agent_id, message_id)` | Full message with body + verdicts. |
| `PostboxClient.list_inboxes(agent_id?)` | Provisioned inboxes. |
| `plus_address(base_inbox, task_id)` | Build `base+task@domain`. |
| `parse_plus_address(addr)` | `{base, task_id, domain}` or `None`. |
| `verify_signature(raw_body, signature, secret)` | Timing-safe HMAC-SHA256 check on an inbound webhook. |
| `parse_inbound_event(raw_body)` | Raw bytes/dict → `InboundEvent`. |

An `AsyncPostboxClient` mirrors the same surface on `httpx.AsyncClient`.

## Retries

Retries automatically on 429 and 5xx with exponential backoff and
respect for `Retry-After`. Default max 3 retries. Tune with
`PostboxClient(max_retries=…)`.

## Webhook verification

```python
from ujex_postbox import verify_signature, parse_inbound_event

ok = verify_signature(
    raw_body=request.body,                      # bytes
    signature=request.headers["x-ujex-signature"],
    secret=os.environ["POSTBOX_INGEST_SECRET"],
)
if not ok:
    return Response(status=401)
event = parse_inbound_event(request.body)
if event.injection_score.score > 0.7:
    # flag for review, don't let the agent process it yet
    quarantine(event)
```

## Framework integrations

Each adapter is an opt-in install that raises a clear `ImportError` with
install instructions if the framework isn't present.

### LangChain

```bash
pip install 'ujex-postbox[langchain]'
```

```python
from ujex_postbox import PostboxClient
from ujex_postbox.integrations.langchain import postbox_tools

client = PostboxClient(device_key="ap_live_...", agent_id="agent-hello")
tools = postbox_tools(client, agent_id="agent-hello")
# hand `tools` to any LangChain agent / graph
```

### LlamaIndex

```bash
pip install 'ujex-postbox[llamaindex]'
```

```python
from ujex_postbox import PostboxClient
from ujex_postbox.integrations.llamaindex import postbox_tools

client = PostboxClient(device_key="ap_live_...", agent_id="agent-hello")
tools = postbox_tools(client, agent_id="agent-hello")
```

### CrewAI

```bash
pip install 'ujex-postbox[crewai]'
```

```python
from ujex_postbox import PostboxClient
from ujex_postbox.integrations.crewai import postbox_tools

client = PostboxClient(device_key="ap_live_...", agent_id="agent-hello")
send_tool, read_tool, list_tool = postbox_tools(client, agent_id="agent-hello")
```

## Core loop

Use Python for the Postbox edge of the golden path: send/receive agent mail,
request human approval with `require_human=True`, and inspect prompt-injection
signals on inbound messages. Recall memory and owner-scoped Audit are available
through the dashboard, CLI, and TypeScript client while the Python SDK remains
Postbox-focused.

Generate one stable `session_id` per agent run and pass it to every outbound
send. Ujex uses it for prompt-injection poisoning, exfiltration checks, and
budget preflight.

## No account yet?

Free tier: 100 sends/month plus approvals, audit, and Recall memory, no card. Sign up at
[ujex.dev](https://ujex.dev).

## License

Apache-2.0
