Posting Messages
Different ways to render and send messages with thread.post().
thread.post() accepts several message formats, each suited to different use cases. Choose the format that best fits your content — from plain strings to structured AST to rich interactive cards.
Plain text
The simplest option. Pass a string and it goes through as-is to the platform.
await thread.post("Hello world");This sends the string directly without any formatting conversion.
Markdown
Pass a { markdown } object to have the SDK convert standard markdown to each platform's native format — mrkdwn for Slack, HTML for Teams, and so on.
await thread.post({
markdown: "**Bold**, _italic_, and `code`",
});Under the hood, the SDK parses the markdown into an mdast AST, then each adapter converts it to the platform's format.
AST builders
For programmatic control over message formatting, use the mdast AST builder functions exported from chat. This is the recommended approach for most use cases — it gives you fine-grained control without the overhead of card rendering.
import { root, paragraph, text, strong, link } from "chat";
await thread.post({
ast: root([
paragraph([
strong([text("Deployment complete")]),
text(" — "),
link("https://example.com", [text("View site")]),
]),
]),
});Available builders
| Builder | Description | Example |
|---|---|---|
root(children) | Root node (required wrapper) | root([paragraph([...])]) |
paragraph(children) | Paragraph block | paragraph([text("Hello")]) |
text(value) | Plain text | text("Hello") |
strong(children) | Bold text | strong([text("bold")]) |
emphasis(children) | Italic text | emphasis([text("italic")]) |
strikethrough(children) | strikethrough([text("done")]) | |
inlineCode(value) | Inline code | inlineCode("const x = 1") |
codeBlock(value, lang?) | Fenced code block | codeBlock("const x = 1", "ts") |
link(url, children, title?) | Hyperlink | link("https://...", [text("click")]) |
blockquote(children) | Block quote | blockquote([paragraph([text("...")])]) |
Parsing markdown to AST
You can also parse a markdown string into an AST, manipulate it, then send it:
import { parseMarkdown, stringifyMarkdown } from "chat";
const ast = parseMarkdown("**Hello** world");
// Manipulate the AST...
await thread.post({ ast });Cards
When you need interactive elements like buttons, dropdowns, or structured layouts, use cards. Cards render natively on each platform — Block Kit on Slack, Adaptive Cards on Teams, and Google Chat Cards.
Function syntax
Use the function-call API for type-safe card construction:
import { Card, Text, Actions, Button } from "chat";
await thread.post(
Card({
title: "Order #1234",
children: [
Text("Your order has been received!"),
Actions([
Button({ id: "approve", label: "Approve", style: "primary" }),
Button({ id: "reject", label: "Reject", style: "danger" }),
]),
],
})
);JSX syntax
You can also use JSX if you configure the chat JSX runtime:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "chat"
}
}import { Card, CardText, Actions, Button } from "chat";
await thread.post(
<Card title="Order #1234">
<CardText>Your order has been received!</CardText>
<Actions>
<Button id="approve" style="primary">Approve</Button>
<Button id="reject" style="danger">Reject</Button>
</Actions>
</Card>
);The JSX syntax requires jsxImportSource: "chat" in your tsconfig.json (or a per-file /** @jsxImportSource chat */ pragma). Without this, TypeScript won't recognize the card JSX types. If you run into type issues with JSX, use the function-call syntax instead — it produces the same output with better type inference.
See the Cards page for the full list of card components.
Streaming
Pass an AsyncIterable<string> (like the AI SDK's textStream) to stream a message in real time. The SDK uses platform-native streaming where available and falls back to post-then-edit on other platforms.
import { streamText } from "ai";
const result = streamText({ model, prompt: message.text });
await thread.post(result.textStream);See the Streaming page for details on platform behavior and configuration.
Attachments and files
Any structured message format (markdown, ast, or card) supports files for uploading attachments alongside the message:
await thread.post({
markdown: "Here's the report:",
files: [{ data: buffer, filename: "report.pdf" }],
});See the Files page for more on attachments.
Choosing a format
| Format | Use when | Example |
|---|---|---|
| Plain string | Simple, unformatted text | Status updates, acknowledgements |
{ markdown } | You have a markdown string (e.g. from a template) | Notifications with links and formatting |
{ ast } | You need programmatic formatting control | Dynamic messages built from data |
| Card (function) | You need buttons, fields, or structured layouts | Approval flows, dashboards |
| Card (JSX) | Same as above, with JSX syntax preference | Same use cases as function cards |
AsyncIterable | Streaming AI responses | Chat with LLMs |
For most cases, AST builders give the best balance of control and simplicity. Reach for cards when you need interactive elements like buttons or dropdowns.