While execute() lets the AI model handle messaging automatically, sometimes you need to send messages directly from your handler. The conversation.send() method gives you full control over what gets sent.
Sending a text message
await conversation.send({
type: "text",
payload: { text: "Hello! How can I help you?" },
})
Message types
The available message types depend on which channel your agent is deployed on. For Webchat, the following types are supported:
| Type | Payload | Description |
|---|
text | { text, value? } | Plain text message |
markdown | { markdown } | Markdown-formatted text |
image | { imageUrl } | Image from a URL |
audio | { audioUrl } | Audio file |
video | { videoUrl } | Video file |
file | { fileUrl, title? } | Downloadable file |
location | { latitude, longitude, address?, title? } | Map location |
card | { title, subtitle?, imageUrl?, actions } | Rich card with action buttons |
carousel | { items } | Horizontal scrollable list of cards |
choice | { text, options, disableFreeText? } | Quick reply buttons |
dropdown | { text, options, disableFreeText? } | Dropdown select menu |
bloc | { items } | A group of mixed message types sent together |
custom | { url, name, data? } | Raw custom message referencing a bundled component by URL |
customComponent | { component, props } | Send a custom component directly. The runtime rewrites this into a custom message under the hood, so you get type-checked props instead of a raw URL |
Other channels (Slack, WhatsApp, Telegram) may support different message types. When you specify a channel on your conversation, TypeScript will only allow the message types that channel supports.
Examples
Card with actions
await conversation.send({
type: "card",
payload: {
title: "Order #1234",
subtitle: "Shipped - arriving Thursday",
imageUrl: "https://example.com/product.jpg",
actions: [
{ action: "url", label: "Track shipment", value: "https://example.com/track/1234" },
{ action: "postback", label: "Contact support", value: "contact_support" },
],
},
})
Carousel
await conversation.send({
type: "carousel",
payload: {
items: [
{
title: "Basic Plan",
subtitle: "$9/mo",
imageUrl: "https://example.com/basic.png",
actions: [{ action: "postback", label: "Select", value: "plan_basic" }],
},
{
title: "Pro Plan",
subtitle: "$29/mo",
imageUrl: "https://example.com/pro.png",
actions: [{ action: "postback", label: "Select", value: "plan_pro" }],
},
],
},
})
await conversation.send({
type: "choice",
payload: {
text: "How would you like to proceed?",
options: [
{ label: "Check order status", value: "check_order" },
{ label: "Start a return", value: "start_return" },
{ label: "Talk to a human", value: "talk_to_human" },
],
},
})
Markdown
await conversation.send({
type: "markdown",
payload: {
markdown: "Here's a summary:\n\n- **Items**: 3\n- **Total**: $45.99\n- **Status**: Processing",
},
})
Typing indicators
The framework automatically starts a typing indicator when your handler begins and stops it when the handler finishes. You don’t need to manage this yourself.
If you need manual control (e.g., you stopped typing to send a message and want to restart it before a slow operation), you can call the methods directly:
await conversation.startTyping()
await conversation.stopTyping()
Read and write metadata tags on the conversation:
const priority = conversation.tags.priority
conversation.tags.priority = "high"
conversation.tags.category = "billing"
Tags must be declared in agent.config.ts under conversation.tags. Check out our guide to configuration your agent for more information.
Loading a conversation by ID
You can send messages to any conversation, not just the current one:
import { Conversation } from "@botpress/runtime"
const otherConversation = await Conversation.get("conversation-id")
await otherConversation.send({
type: "text",
payload: { text: "Hello from another conversation!" },
})
This is useful for cross-conversation notifications or broadcasting messages.
Combining with execute()
A common pattern is to send a direct message before or after handing control to the AI:
handler: async ({ execute, conversation, type }) => {
if (type === "message") {
await conversation.send({
type: "text",
payload: { text: "Let me look into that for you." },
})
await execute({
instructions: "You are a helpful assistant.",
})
}
}
Direct sends and AI-generated messages appear in the same conversation. Last modified on April 24, 2026