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"],
)

Slack params

Slack replies only accept text. CC, BCC, subject, and HTML are rejected as invalid for Slack.

await event.reply(text="👀 on it. will report back.")

The reply is posted via chat.postMessage back into the channel the event came from, threaded under the originating message (thread_ts set to the event’s thread_ts if present, otherwise the message’s ts — i.e. replies always attach to the conversation, not the channel’s main feed).

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, email and slack accept replies. webhook and cron do not — there’s no inbound conversation to reply into.

Limits

Email replies are capped to protect customer domain reputation:

FieldLimit
cc + bcc5 addresses combined.
subject512 characters.
text256 KB.

Exceeding any cap raises RequestError with one of the codes too_many_recipients, subject_too_long, body_too_large.

Threading

Email: no action needed. Nevo stamps the right In-Reply-To and References headers on the reply and adds a Re: prefix when you don’t override the subject. The reply lands in the same conversation in Gmail, Outlook, Apple Mail.

Slack: also automatic. A reply to a top-level @mention starts a new thread under that mention; a reply to a message already in a thread posts into that same thread.

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.