Replies

Send a response on the channel the event arrived on.

Two ways to send a reply — inside a handler, or explicitly with the client.

# Inside a handler — the SDK resolves the active client via the current async context.
@client.on_event()
async def handle(event):
    if event.type == "email.received":
        await event.reply(text="Got it. We're on it.")

# Anywhere you have an event + a client.
await client.reply(event, text="Got it.")

Both return a ReplyReceipt:

receipt = await event.reply(text="hi")
receipt.reply_id      # str — server-side UUID
receipt.accepted_at   # datetime — server's accept time

Email params

Email replies accept these in addition to text:

await event.reply(
    text="Plain-text body (required).",
    html="<p>Optional rich body.</p>",
    subject="Optional subject override (defaults to 'Re: <original>').",
    cc=["ops@acme.com"],
    bcc=["archive@acme.com"],
)

Per-channel param validation

The SDK carries an allowlist per channel type. Passing an email param on a non-email event raises RequestError before any network call — no silent drops:

# event.type == "webhook.received"
await event.reply(text="hi", subject="oops")
# ↑ RequestError: subject is not supported for webhook replies.

Channels that don’t support replies at all raise UnsupportedChannelError:

# event.type == "webhook.received"
await event.reply(text="hi")
# ↑ UnsupportedChannelError: replies are not supported for 'webhook' channels yet.

Today the only channel that accepts replies is email. More soon.

Threading

You don’t need to do anything. The backend reads the original email’s Message-ID, stamps In-Reply-To + References on the reply, and builds the subject with a Re: prefix when you don’t override it. The reply lands in the same conversation in Gmail, Outlook, Apple Mail.

Errors

ExceptionWhenRetry?
UnsupportedChannelErrorChannel doesn’t support replies.No.
RequestErrorClient-side validation or server 4xx.No.
ServerErrorServer 5xx after retries.Yes (later).
NetworkErrorNetwork failure after retries.Yes (later).
AuthErrorToken rejected.No. Fix key.

All inherit from ReplyError. Check .is_transient if you need to gate retry logic:

try:
    await event.reply(text="hi")
except ReplyError as exc:
    if exc.is_transient:
        # queue for a retry later
        ...
    else:
        raise

See Errors for the full hierarchy.