Thread
Represents a conversation thread with methods for posting, subscribing, and state management.
A Thread is provided to your event handlers and represents a conversation thread on any platform. You don't create threads directly — they come from handler callbacks or chat.openDM().
Properties
Prop
Type
post
Post a message to the thread. Accepts strings, structured messages, cards, and streams.
// Plain text
await thread.post("Hello!");
// Markdown
await thread.post({ markdown: "**Bold** text" });
// AST
await thread.post({ ast: root([paragraph([text("Hello")])]) });
// Card
await thread.post(Card({ title: "Hi", children: [Text("Hello")] }));
// Stream (fullStream recommended for multi-step agents)
await thread.post(result.fullStream);Parameters: message: string | PostableMessage | CardJSXElement
Returns: Promise<SentMessage> — the sent message with edit(), delete(), addReaction(), and removeReaction() methods.
See Posting Messages for details on each format.
postEphemeral
Post a message visible only to a specific user.
await thread.postEphemeral(userId, "Only you can see this", {
fallbackToDM: true,
});Prop
Type
Returns: Promise<EphemeralMessage | null>
schedule
Schedule a message for future delivery. Currently only supported by the Slack adapter — other adapters throw NotImplementedError.
const scheduled = await thread.schedule("Reminder: standup in 5 minutes!", {
postAt: new Date("2026-03-09T09:00:00Z"),
});
// Cancel before it's sent
await scheduled.cancel();Parameters: message: string | PostableMessage | CardJSXElement, options: { postAt: Date }
Returns: Promise<ScheduledMessage>
Streaming and file uploads are not supported in scheduled messages.
subscribe / unsubscribe
Manage thread subscriptions. Subscribed threads route all messages to onSubscribedMessage handlers.
await thread.subscribe();
await thread.unsubscribe();
const subscribed = await thread.isSubscribed();Subscriptions persist across restarts via your state adapter.
state
Store typed, per-thread state that persists across requests. State has a 30-day TTL.
// Read state
const state = await thread.state; // TState | null
// Merge into existing state
await thread.setState({ aiMode: true });
// Replace state entirely
await thread.setState({ aiMode: false }, { replace: true });startTyping
Show a typing indicator in the thread. No-op on platforms that don't support it. On Slack, you can pass an optional status string to show a custom loading message (requires assistant:write scope).
await thread.startTyping();
// With custom status (Slack only)
await thread.startTyping("Searching documents...");messages / allMessages
Iterate through message history.
// Newest first (auto-paginates)
for await (const msg of thread.messages) {
console.log(msg.text);
}
// Oldest first (auto-paginates)
for await (const msg of thread.allMessages) {
console.log(msg.text);
}refresh
Re-fetch messages from the API and update recentMessages.
await thread.refresh();mentionUser
Get a platform-specific @-mention string for a user.
await thread.post(`Hey ${thread.mentionUser(userId)}, check this out!`);Serialization
Threads can be serialized for workflow engines and external systems. The serialized thread includes the current message if one is available.
// Serialize
const json = thread.toJSON();
// Pass to a workflow
await workflow.start("my-workflow", {
thread: thread.toJSON(),
});The serialized format includes the thread ID, channel ID, adapter name, DM status, and the current message (if present).
Deserialization
Use bot.reviver() as a JSON.parse reviver to automatically restore Thread and Message objects from serialized payloads:
const data = JSON.parse(payload, bot.reviver());
await data.thread.post("Hello from workflow!");Under the hood, the reviver calls ThreadImpl.fromJSON() and Message.fromJSON() for any serialized objects it encounters.
ScheduledMessage
Returned by thread.schedule() and channel.schedule().
Prop
Type
SentMessage
Returned by thread.post(). Extends Message with mutation methods.
Prop
Type