Skip to main content

zeno-channel-email

Email channel for Zeno backed by Resend. Hosts a Starlette + uvicorn server for Resend's inbound webhook and posts outbound replies (with correct RFC 5322 threading headers) to Resend's REST API.

Verifies inbound webhooks with Svix-style HMAC-SHA256 signatures and enforces a sender allowlist. Replies to an inbound email land in the same thread in Gmail, Outlook, and Apple Mail because the channel owns the Message-ID / In-Reply-To / References header math.

Install

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

Minimal usage

from zeno.channels.email import EmailChannel

channel = EmailChannel(
    api_key="re_...",                                    # Resend API key
    signing_secret="whsec_...",                          # Resend webhook secret
    from_address="zeno@mail.example.com",
    allowed_senders=("you@example.com",),
    host="127.0.0.1",
    port=8080,
    webhook_path="/webhook",
)
# Then pass it to ZenoApp(channels=[channel]) exactly like any Channel.

The signing secret rotation flow uses Svix's multi-secret window: add the new secret in the Resend dashboard first, restart the process with the new env var, then remove the old secret. No downtime.

See apps/zeno-example-chat for a runnable reference wiring that turns the email channel on only when the relevant env vars are set.

Behavior

  • Signature verification: Resend signs inbound webhooks with Svix. Bad or missing signature → 401. Every other drop (stale timestamp, unknown event type, unallowlisted sender, malformed JSON, fetch_received failure, queue full) returns 200 so Resend doesn't retry and so the 401 audit signal stays meaningful.
  • Two-phase inbound: Resend's email.received webhook carries metadata only. The channel GETs /emails/received/{email_id} for the body and headers before enqueuing an IncomingMessage.
  • Inbound → IncomingMessage: user_id = normalized lower-case From: address (parsed with email.utils.parseaddr to defeat display-name injection), text = preferred text/plain body with HTML fallback, thread_key = same as user_id so replies route back. RFC-5322 headers are cached per-thread in-process so outbound replies can build correct In-Reply-To / References. Attachments on inbound are ignored in 0.7.0.
  • Outbound: text + optional attachments via POST /emails. multipart/alternative is synthesized from the agent's plaintext reply (no user-authored HTML templates). Message-ID is generated per reply. References chain is capped at 50 entries to defend against crafted-header DoS.
  • Capabilities: supports_threading=True, supports_images=False (attachments flow outbound only in 0.7.0).

Security notes

  • signing_secret and api_key are marked repr=False on the channel dataclass so they don't leak in traceback / debug output.
  • The 300 s Svix timestamp window is mandatory (replay defense).
  • allowed_senders is normalized to lower-case at construction; inbound From: is also normalized so the allowlist check is case-insensitive.
  • Resend's POST /emails caps a single email at 40 MB (attachments counted after base64 encoding).

Testing

uv run pytest packages/zeno-channel-email

Tests use httpx.MockTransport for Resend's REST surface and the Starlette TestClient for the router. No Resend account required in CI.

See also

Part of the Zeno framework.

Metadata

Release files for zeno-channel-email 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zeno-channel-email 1.1.0
File Size Uploaded
zeno_channel_email-1.1.0.tar.gz 27.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zeno-channel-email 1.1.0
File Interpreter ABI Platform
zeno_channel_email-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 47.0 kB

Release files / zeno_channel_email-1.1.0.tar.gz

Download URL zeno_channel_email-1.1.0.tar.gz
Size 27.7 kB
Tags Source
SHA-256 checksum
How to use checksums
2a43c1a7fc7d70556ef07244601b98798086f87d27c2776eb190c7a3b55069e5
BLAKE2b-256 checksum
How to use checksums
2f9b4aafdce476cca41823d36d93766b535b61e2927857fa73bf6faa23c22e37
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 1, 2026.

Transparency log

Release files / zeno_channel_email-1.1.0-py3-none-any.whl

Download URL zeno_channel_email-1.1.0-py3-none-any.whl
Size 19.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d432a1c0696c2f8672f4ca334e65b0624e193a9b2682287dcc9cd99487b649b8
BLAKE2b-256 checksum
How to use checksums
41ca8de92e5311c17ac0a4bc7bd84691614690cac1b7cdc3b26113ea7f404138
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page