Metadata-Version: 2.4
Name: zeno-channel-sendblue
Version: 1.1.0
Summary: Sendblue iMessage webhook channel for Zeno.
Project-URL: Homepage, https://github.com/nkootstra/zeno
Project-URL: Repository, https://github.com/nkootstra/zeno
Project-URL: Issues, https://github.com/nkootstra/zeno/issues
Project-URL: Changelog, https://github.com/nkootstra/zeno/blob/main/CHANGELOG.md
Author: Niels Kootstra
License-Expression: MIT
License-File: LICENSE
Keywords: agent,ai,channel,imessage,sendblue,webhook,zeno
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: starlette<2,>=1.0
Requires-Dist: uvicorn<1,>=0.44
Requires-Dist: zeno-core
Description-Content-Type: text/markdown

# zeno-channel-sendblue

Sendblue iMessage webhook channel for [Zeno](https://github.com/nkootstra/zeno). The first
webhook-style channel — use it as the template for any `Channel` that
hosts its own HTTP inbound transport.

Hosts a Starlette + uvicorn server for Sendblue's inbound webhook and
posts outbound messages to Sendblue's REST API. Verifies the shared-secret
header, enforces a phone-number allowlist, and fires a typing indicator
per accepted inbound message.

## Install

```bash
uv add 'zeno-framework[sendblue]'
# or, without the AI package:
uv add zeno-channel-sendblue
```

## Minimal usage

```python
from zeno.channels.sendblue.channel import SendblueChannel

channel = SendblueChannel(
    api_key_id="...",
    api_secret="...",
    from_number="+15550001111",
    signing_secret="...",            # shared with Sendblue dashboard
    allowed_numbers=("+31600000000",),
    host="127.0.0.1",
    port=8080,
    webhook_path="/webhook",
)
# Then pass it to ZenoApp(channels=[channel]) exactly like any Channel.
```

See [`apps/zeno-example-imessage`](https://github.com/nkootstra/zeno/blob/main/apps/zeno-example-imessage/README.md)
for the complete runnable reference app, including ngrok / Cloudflare
Tunnel setup notes.

## Behavior

- **Signature verification**: Sendblue echoes the configured secret in
  the `sb-signing-secret` header. Bad or missing signature → `401`.
  Every other drop (non-iMessage service, group message, unallowlisted
  sender, malformed JSON, queue full) returns `200` so Sendblue doesn't
  retry and so the 401 audit signal stays meaningful.
- **Inbound → `IncomingMessage`**: `user_id = from_number` (E.164),
  `text = content`, `thread_key = from_number` so replies route back.
  A non-empty `media_url` becomes an image `Attachment`.
- **Outbound**: text + optional single image via
  `POST /api/send-message`. Content over 18,996 chars is truncated
  (WARNING log) to fit Sendblue's limit.
- **Capabilities**: `supports_images=True`, `supports_typing=True`,
  others False.

## Testing

```bash
uv run pytest packages/zeno-channel-sendblue
```

Tests use `httpx.MockTransport` for outbound and the Starlette
`TestClient` for the router. No Sendblue secrets required in CI.

## See also

- [`zeno-channel-cli`](../zeno-channel-cli/README.md), [`zeno-channel-email`](../zeno-channel-email/README.md) — other first-party channels.
- [`docs/adapters.md`](../../docs/adapters.md) — writing your own `Channel`.

Part of the [Zeno framework](https://github.com/nkootstra/zeno).
