Metadata-Version: 2.4
Name: letterapp
Version: 0.1.0
Summary: Official Python client for letter.app - onboarding email drip campaigns. Auto-batching, retries, idempotency.
Project-URL: Homepage, https://letter.app/docs/python-sdk
Project-URL: Repository, https://github.com/vincenzor/letter-python
Project-URL: Issues, https://github.com/vincenzor/letter-python/issues
Author: letter.app
License-Expression: MIT
License-File: LICENSE
Keywords: analytics,drip,email,identify,ingestion,letter,letterapp,onboarding,sdk,track
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 :: Only
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# letterapp (Python)

[![PyPI version](https://img.shields.io/pypi/v/letterapp)](https://pypi.org/project/letterapp/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green)](./LICENSE)

Official Python client for **[letter.app](https://letter.app)** - onboarding
email drip campaigns for product teams.

```bash
pip install letterapp
```

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

## Quick start

```python
import os
from letterapp import Letter

letter = Letter(api_key=os.environ["LETTER_API_KEY"])  # Dashboard -> Settings -> API keys

# Tell Letter who your user is (call where users sign up or log in).
letter.identify(
    user_id="user_123",
    email="alice@example.com",
    traits={"name": "Alice", "plan": "free"},
)

# Report something they did.
letter.track(user_id="user_123", event="Signed Up", properties={"source": "web"})

# Required before the process exits so no events are lost.
letter.close()
```

Or use it as a context manager, which flushes on exit:

```python
with Letter(api_key=os.environ["LETTER_API_KEY"]) as letter:
    letter.track(user_id="user_123", event="Workspace Created")
```

## Serverless (Lambda, Cloud Functions)

There is no background time to flush in a serverless handler, so set
`flush_at=1` and `flush()` at the end of each invocation:

```python
letter = Letter(api_key=os.environ["LETTER_API_KEY"], flush_at=1)

def handler(event, context):
    letter.track(user_id="user_123", event="Checkout Started")
    letter.flush()
```

## What it does

- **Auto-batching** - calls are queued and flushed every 100ms or 50 events by
  a background daemon thread.
- **Retries** - `429` waits `Retry-After`; `5xx` and network errors back off
  exponentially with jitter, up to `max_retries` (default 3).
- **Idempotent** - every call gets a UUID `message_id` so retries are
  deduplicated server-side.
- **No dependencies** - HTTP over the standard library `urllib`.

## API

```python
Letter(
    api_key,
    base_url="https://api.letter.app",  # only set for self-hosted / local
    flush_at=50,                          # 1 for serverless
    flush_interval=0.1,                   # seconds
    max_retries=3,
    timeout=10.0,
    on_error=None,                        # callback(Exception) for bg errors
)

letter.identify(user_id, email=None, traits=None, timezone=None, timestamp=None, message_id=None)
letter.group(user_id, account_id, name=None, traits=None, timestamp=None, message_id=None)
letter.track(user_id, event, properties=None, timestamp=None, message_id=None)
letter.flush()   # send queued calls now, block until done
letter.close()   # flush + stop the background thread (also runs at exit)
```

Configuration errors and non-retryable API responses raise `LetterError`
(with `.status` and `.body`). Background transport errors are passed to
`on_error` instead, since they cannot be raised to the caller.

## Full documentation

- **SDK reference:** <https://letter.app/docs/python-sdk>
- **Ingestion API:** <https://letter.app/docs/api>

## License

MIT - see [LICENSE](./LICENSE).
