client.on(async (event) => {
if (event.type === "email.received") {
await client.reply(event, { text: "Got it. We're on it." });
}
});
reply() returns a ReplyReceipt:
const receipt = await client.reply(event, { text: "hi" });
receipt.replyId; // server-side UUID
receipt.acceptedAt; // Date — server's accept time
Email params
await client.reply(event, {
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 client.reply(event, { text: "👀 on it. will report back." });
The reply is posted back into the channel the event came from, threaded under the originating message.
Per-channel validation
The SDK validates params against the channel type before hitting the network:
// event.type === "webhook.received"
await client.reply(event, { text: "hi" });
// ↑ UnsupportedChannelError: replies are not supported for "webhook" channels yet.
// event.type === "slack.event"
await client.reply(event, { text: "hi", subject: "oops" });
// ↑ RequestError: subject is not supported for slack replies.
Today, email and slack accept replies. webhook and cron do not.
Limits
Email replies are capped to protect customer domain reputation:
| Field | Limit |
|---|---|
cc + bcc | 5 addresses combined. |
subject | 512 characters. |
text | 256 KB. |
Exceeding any cap returns RequestError with one of:
too_many_recipients, subject_too_long, body_too_large.
Threading
Email: no action needed. Nevo stamps In-Reply-To + References and adds a
Re: prefix when you don’t override the subject.
Slack: also automatic. Replies to a top-level @mention start a thread
under it; replies to threaded messages post into the same thread.
Errors
| Exception | When | Retry? |
|---|---|---|
UnsupportedChannelError | Channel doesn’t support replies. | No. |
RequestError | Client-side validation or server 4xx. | No. |
ServerError | Server 5xx after retries. | Yes (later). |
NetworkError | Network failure after retries. | Yes (later). |
AuthError | Token rejected. | No. Fix key. |
All inherit from ReplyError. Check .isTransient if you need to gate
retry logic:
try {
await client.reply(event, { text: "hi" });
} catch (err) {
if (err instanceof ReplyError && err.isTransient) {
// queue for a retry later
} else {
throw err;
}
}
See Errors for the full hierarchy.