Channels overview
Added in: @mastra/core@1.22.0
Channels connect agents to messaging and collaboration platforms like Slack, Microsoft Teams, Discord, Telegram, WhatsApp, GitHub, and Linear. When a user sends a message or comment on a platform, the agent receives it, processes it through the normal agent pipeline, and streams the response back to the conversation.
Start with the page for your platform:
Mastra channels work with compatible Chat SDK adapters beyond the platforms listed here. The More page lists additional adapters and explains how the same Mastra-side pattern applies.
When to use channelsDirect link to When to use channels
Use channels when an agent needs to:
- Meet users where they already talk or work.
- Respond in chat platforms like Slack, Microsoft Teams, Discord, Telegram, and WhatsApp.
- Support multiplayer agents where several people interact with the same agent in a shared channel or thread.
- Integrate with collaboration workflows like GitHub issues, pull request threads, and Linear comments.
Configure an agentDirect link to Configure an agent
Configure channels directly on your agent using adapters from the Chat SDK. The example below uses Slack, but other adapters follow the same shape: import the adapter factory, add it to channels.adapters, and use the adapter key in the generated webhook route.
import { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support Agent',
instructions: 'You are a helpful support assistant.',
model: 'openai/gpt-5.5',
channels: {
adapters: {
slack: createSlackAdapter(),
},
},
})
Register the agent in your Mastra instance with storage so channel state persists across restarts:
import { Mastra } from '@mastra/core'
import { LibSQLStore } from '@mastra/libsql'
import { supportAgent } from './agents/support-agent'
export const mastra = new Mastra({
agents: { supportAgent },
storage: new LibSQLStore({
url: process.env.DATABASE_URL,
}),
})
Each Chat SDK adapter documents the environment variables it reads by default, such as bot tokens, signing secrets, app IDs, and webhook verification tokens. Use the page for your platform in this section, or the Chat SDK adapter catalog, to find the exact variable names.
Webhook routesDirect link to Webhook routes
Platforms send channel activity to Mastra through webhooks. A webhook is an HTTP endpoint that the platform calls when something happens, such as a new message, a mention, or a user selecting "Approve" on an interactive tool approval card.
Mastra generates a webhook route for each configured adapter:
/api/agents/{agentId}/channels/{platform}/webhook
For example, a Slack adapter on an agent with the support-agent ID uses:
/api/agents/support-agent/channels/slack/webhook
Point the platform's webhook, event, or interactions URL to this path. The Chat SDK adapter verifies the incoming request, normalizes the event, and passes it to the agent.
During local development, platform webhooks need a public URL to reach your local server. Use a tunnel like cloudflared or ngrok to expose localhost:4111:
- npm
- pnpm
- Yarn
- Bun
npx cloudflared tunnel --url http://localhost:4111
pnpm dlx cloudflared tunnel --url http://localhost:4111
yarn dlx cloudflared tunnel --url http://localhost:4111
bun x cloudflared tunnel --url http://localhost:4111
ngrok http 4111
Use the generated public URL as the base URL for webhook paths, for example https://abc123.trycloudflare.com/api/agents/support-agent/channels/slack/webhook.
Tunnel URLs are for local development. After you deploy the Mastra server, update the platform's webhook, event, or interactions URL to your production URL.
Thread contextDirect link to Thread context
When a user mentions the agent mid-conversation in a channel thread, the agent may not have prior context. By default, Mastra fetches the last 10 messages from the platform on the first mention.
- On the first mention in a thread, the agent fetches recent messages from the platform.
- These messages are prepended to the user's message as conversation context.
- After responding, the agent subscribes to the thread and has full history through Mastra memory.
- Subsequent messages in that thread don't re-fetch from the platform.
Set threadContext: { maxMessages: 0 } to disable this behavior. This only applies to non-direct message threads.
Mastra also adds a short system message that tells the agent which channel and platform the request came from, such as whether the message came from a direct message or public channel. Set threadContext: { addSystemMessage: false } to skip it.
Tool approvalDirect link to Tool approval
Tools with requireApproval: true render as interactive cards with Approve and Deny buttons:
import { promises as fs } from 'node:fs'
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const deleteFile = createTool({
id: 'delete-file',
description: 'Delete a file from the system',
inputSchema: z.object({
path: z.string().describe('Path to the file to delete'),
}),
requireApproval: true,
execute: async ({ path }) => {
await fs.unlink(path)
return { deleted: path }
},
})
When the agent calls this tool, users see a card with the tool name, arguments, and Approve and Deny actions. The tool only executes after approval.
Set toolDisplay: 'text' on an adapter to render tool calls as plain text instead of interactive cards. In 'hidden' mode, autoResumeSuspendedTools can resume suspended tools when a later user message arrives on the same thread. This requires memory. Hidden mode only suppresses the approval buttons.
Multi-user awarenessDirect link to Multi-user awareness
In group conversations, Mastra prefixes each message with the sender's name and platform ID so the agent can distinguish between speakers:
[Alice (@U123ABC)]: Can you help me with this?
[Bob (@U456DEF)]: I have a question too.
Multimodal contentDirect link to Multimodal content
Models like Gemini can process images, video, and audio natively. Combine inlineMedia and inlineLinks to let users share rich content with your agent across platforms:
import { Agent } from '@mastra/core/agent'
import { createDiscordAdapter } from '@chat-adapter/discord'
export const visionAgent = new Agent({
id: 'vision-agent',
name: 'Vision Agent',
instructions: 'You can see images, watch videos, and listen to audio.',
model: 'google/gemini-2.5-flash',
channels: {
adapters: {
discord: createDiscordAdapter(),
},
inlineMedia: ['image/*', 'video/*', 'audio/*'],
inlineLinks: [
{ match: 'youtube.com', mimeType: 'video/*' },
{ match: 'youtu.be', mimeType: 'video/*' },
'imgur.com',
],
},
})
With this configuration:
- A user uploads a screenshot and the agent describes what it sees.
- A user uploads an
.mp4clip and the agent summarizes the video. - A user pastes a YouTube link and the agent watches and discusses the video.
- A user pastes an imgur link and the agent sees the image directly.
By default, only images are sent inline (inlineMedia: ['image/*']). Unsupported types are described as text summaries so the agent knows about the file without failing on models that reject them.
See Channels reference for all inlineMedia patterns and inlineLinks reference for domain matching, HEAD detection, and forced MIME types.
Serverless deploymentDirect link to Serverless deployment
On serverless platforms like Vercel, each request runs in a separate, short-lived instance. Channels need two things to work reliably in that environment: a way to keep the function alive while the agent responds, and a shared pub/sub so instances can coordinate.
Keep the function alive with waitUntilDirect link to keep-the-function-alive-with-waituntil
A channel webhook returns a 200 response right away, then the agent runs in the background to post its reply. On most serverless platforms the function is frozen as soon as it responds, which stops the run before the agent answers. Pass a waitUntil function so the platform keeps the instance alive until the run finishes.
On Vercel, pass waitUntil from @vercel/functions:
import { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
import { waitUntil } from '@vercel/functions'
export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support Agent',
instructions: 'You are a helpful support assistant.',
model: 'openai/gpt-5.5',
channels: {
adapters: {
slack: createSlackAdapter(),
},
waitUntil,
},
})
Vercel and AWS Lambda require waitUntil, since they freeze the function as soon as the response is sent. Cloudflare Workers and Netlify Functions are detected automatically from the request context, so they don't need it. For runtimes where waitUntil lives on the request context but isn't detected automatically, use resolveWaitUntil. See the Channels reference for details.
Coordinate instances with a shared pub/subDirect link to Coordinate instances with a shared pub/sub
Channels route messages through the agent's signal pipeline, and each run acquires a lease on its thread so one run owns the conversation at a time. The default in-memory pub/sub can't cross instance boundaries, so on serverless a follow-up message can land on a different instance than the one running the agent. Without a shared pub/sub, that instance can't reach the active run and starts its own, leaving the original run untouched and the thread processed twice.
Configure a shared pub/sub backed by Redis Streams on the Mastra instance so leases and signals coordinate across instances:
import { Mastra } from '@mastra/core'
import { RedisStreamsPubSub } from '@mastra/redis-streams'
export const mastra = new Mastra({
agents: { supportAgent },
pubsub: new RedisStreamsPubSub({
url: process.env.REDIS_URL,
keyPrefix: 'mastra:my-app',
}),
})
Vercel's managed Redis integration and Upstash Redis both work well. For more on when a distributed pub/sub is needed, see the PubSub guide and the RedisStreamsPubSub reference.