Metadata-Version: 2.4
Name: postbox-sdk
Version: 0.1.0
Summary: Official Postbox SDK for Python — mailbox infrastructure for developers.
Project-URL: Homepage, https://postboxapp.cloud
Project-URL: Repository, https://github.com/postboxapp/postbox
Author: Postbox
License: MIT
License-File: LICENSE
Keywords: api,email,mailbox,postbox,sdk,smtp,transactional-email,webhooks
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# Postbox — Python SDK

Official Python SDK for [Postbox](https://postboxapp.cloud) — mailbox
infrastructure for developers. Provision real mailboxes over REST, send/receive
mail, stream events, and verify webhooks.

> Behaviour parity: this SDK implements the same [SDK core contract](../../spec/20-sdk-core-contract.md)
> as the TypeScript reference — identical retry counts, backoff, idempotency-key
> reuse, error taxonomy, SSE reconnect, and webhook verification.

## Install

```bash
pip install postbox-sdk
```

Requires Python 3.11+. Zero runtime dependencies (standard library only).

## Quickstart

```python
from postbox import Postbox

pb = Postbox("pb_live_…")

# Send a message (POST → auto idempotency key, retried safely on network blips)
pb.messages.send({
    "from": "hello@yourdomain.dev",
    "to": [{"address": "user@example.com"}],
    "subject": "Welcome",
    "bodyText": "Thanks for signing up.",
})

# Fetch a mailbox
mailbox = pb.mailboxes.get("mbx_123")

# List (query params are forwarded as-is)
pb.mailboxes.list(query={"domainId": "dom_1"})
```

### Configuration

```python
pb = Postbox(
    "pb_live_…",
    base_url="https://api.postboxapp.cloud/v1",  # override for self-host / staging
    project_id="proj_…",                          # sets X-Project-Id on every request
    timeout=30.0,                                  # per-attempt timeout (seconds)
    max_retries=2,                                 # extra attempts after the first (≤3 total)
    default_headers={"X-Trace": "…"},
    transport=my_transport,                        # inject a custom HttpTransport / proxy
)
```

A missing/blank `api_key` raises `ValueError` at construction — never on the first request.

## Streaming events (SSE)

```python
for event in pb.events.stream():
    print(event.event, event.data)
```

The stream auto-reconnects on drop and resumes from the last seen event id
(`Last-Event-ID`). Break out of the loop (or pass a `threading.Event` as
`cancel=`) to stop.

## Pagination

The API is page-based (`{items, totalPages}`). `Paginator` walks pages lazily:

```python
from postbox import Page, Paginator

page = Page.from_dict(raw_response)
for item in Paginator(page, fetch_next):  # yields across every page
    ...
```

## Webhook verification

```python
from postbox import verify_webhook, SignatureError

try:
    event = verify_webhook(
        raw_body,                      # the RAW request body (str or bytes)
        request.headers["X-Postbox-Signature"],
        request.headers["X-Postbox-Timestamp"],
        webhook_secret,
    )
except SignatureError:
    return 400  # bad signature, stale timestamp, or malformed body
```

Verification is timing-safe (HMAC-SHA256 over `f"{timestamp}.{raw_body}"`),
replay-protected (±300s tolerance), and returns the parsed event — never a
boolean you might forget to check.

## Errors

Every failure is a typed subclass of `PostboxError`, carrying `status`, `code`,
`request_id`, and (for validation) `issues`:

| Type | Status |
|---|---|
| `AuthenticationError` | 401 |
| `PermissionError` | 403 |
| `NotFoundError` | 404 |
| `ConflictError` | 409 |
| `ValidationError` | 400 / 422 |
| `RateLimitError` | 429 (carries `retry_after`) |
| `ServerError` | 5xx (except 501) |
| `NotImplementedError` | 501 |
| `TimeoutError` | per-attempt timeout |
| `NetworkError` | DNS / connection / TLS / reset |
| `SignatureError` | webhook verification |

The mapping is frozen and identical across every Postbox SDK language.

## Architecture

- `postbox/` — the hand-written **core** (`_transport`, `_errors`, `_config`,
  `_pagination`, `_sse`, `_webhooks`, `_client`). This is the ~20% implemented
  per language against the core contract.
- `postbox/_generated/` — the **resource layer**, produced by
  [`@postbox/sdk-gen`](../../packages/sdk-gen) from the OpenAPI specs. Do not
  edit by hand; regenerate with `pnpm --filter @postbox/sdk-gen generate`.

## Development

```bash
python -m pytest        # run the test suite (uses an injected fake transport; no network)
python -m py_compile postbox/*.py postbox/_generated/*.py
```

## License

MIT
