Skip to main content
Send Message lets a running agent reach the user proactively on their configured channels — without waiting for the next user turn. Need the answer back, not just delivery? See Ask Conversation.
The user starts a long job; the agent proactively messages them on Telegram, Slack, or email when it finishes.

Quick Start

1

Give Your Agent the Tool

The agent can now call send_message at any point mid-task to push a notification to the user.
2

List Available Targets

Use action="list" first when you want the agent to pick the right channel rather than defaulting to "origin".
3

Send with a File Attachment

Append MEDIA:<path> to the message text to attach a local file. Multiple attachments are supported.
Media attachments aren’t delivered yet. The text portion is sent, the MEDIA: paths are parsed and counted, and the result detail confirms how many files were skipped. File delivery will land in a future transport update — track PraisonAI#2374 for follow-ups.The MEDIA: caveat applies only to the send_message tool path (via BotOutboundMessenger). Proactive / scheduled / gateway-driven deliveries via DeliveryRouter.send_media do deliver attachments and honour threaded targets — see Outbound Media Delivery.

How It Works

The verbs react, thread, edit, and delete add a capability-gate step: the router checks the adapter’s supports_* flag before dispatch and returns a typed unsupported when the channel lacks the primitive — it never raises.

Targets

Choose the right target form for your situation:

Attachments (MEDIA:)

Append MEDIA:<path> to the message string to attach a local file to the delivery.
File paths must not contain whitespace. The MEDIA: directive is stripped from the displayed message text before delivery.

Reactions

Agents can tap a lightweight ✅ / 👍 / ⏳ reaction on a message instead of sending a full reply — perfect for busy group chats where every reply is noise.
The emoji rides in the message argument and is .strip()-ed — an empty emoji returns {"status": "failed", "detail": "no emoji provided"}.
target="origin" reacts to the inbound message the agent is currently handling — its message_id is carried on the session automatically, so no extra parameter is needed. To react to a different message, use the explicit "<platform>:<chat_id>:<message_id>" form.

Reaction outcomes

Reactions return a JSON string of a ReactionResult with a typed status (plus optional target and detail):
Channels that support reactions today: Telegram, Slack, Discord. Others (WhatsApp, Signal, email, agentmail, Linear) return "unsupported" — the tool never raises. The same Send Policy that guards action="send" also guards react/unreact — a steered or prompt-injected agent cannot react on a channel the operator never intended.

Threads

Open a topic or thread under a target so a long-running effort has its own space.
The thread title rides in the message argument. An empty title returns {"status": "failed", "detail": "no thread title provided"}.

Thread outcomes

Threads return a JSON ThreadResult with a typed status (plus thread_id on success):
Thread-aware routing across platforms is covered in Gateway Threads.

Edit and Delete (own messages)

Update a status in place, or retract a message the agent shouldn’t have sent — instead of spamming follow-ups.
  • Authority: edit and delete are the most-restricted class — own-prior-message. They only work on messages this agent/session sent.
  • Capability gate: powered by the adapter’s supports_edit / supports_delete. Channels that can’t edit/delete return a typed unsupported outcome — the tool never raises.
  • Send-policy still guards edit/delete — a steered agent cannot mutate messages on channels the operator hasn’t allowed.
  • Required message_id: a missing id returns {"status": "failed", "detail": "no message_id provided"}.
  • Result shape: a JSON MessageActionResult with status (ok / unsupported / failed / no_route), optional target, and optional detail.

Channel support today

Per-channel capability flags live in Bot Platform Capabilities.

Action Vocabulary and Authority

Every verb the tool accepts declares one of three authority classes — a closed set that keeps a steered agent from inventing new mutation verbs. Any verb missing from the map is rejected — the agent cannot “invent” a new mutation verb that silently inherits send’s guard:
The same Send Policy guards every write verb. Per-channel primitives are listed in Bot Platform Capabilities.

Listing Targets

action="list" returns a JSON array of all reachable targets so the agent can pick the right one before sending.
Each entry in the array: "observed" means a channel the gateway has seen activity on but is not a configured home or alias.

When It’s Available

When no gateway is active the tool returns:
Agent instructions can check for this string and fall back gracefully.

User Interaction Flow

A user starts a long research task on Telegram, then closes their phone. The agent finishes compiling the report, calls send_message("origin", "Your report is ready MEDIA:/tmp/report.pdf"), and the user receives a Telegram push notification with the text — without ever having to ask “are you done yet?”. A user pings the agent in a 500-person Slack channel. Instead of replying and cluttering the thread, the agent taps a ⏳ reaction to acknowledge, works on the task, then swaps to ✅ when done — no message noise, but the user still sees progress. An agent posts “Working…” on Telegram, keeps working, then edits that same message to “Done ✅” — the user sees a single evolving status line on their phone, not a stream of noisy follow-ups. The agent notices it posted a message with the wrong figure and calls send_message("origin", "", action="delete", message_id="…") to unsend it before the user reads it.

Configuration Reference

send_message takes four arguments. All are optional: action values at a glance:

Common Patterns

Notify on long-task completion

Cross-channel handoff

Pick the target from the list, then send

Acknowledge with a reaction in a busy channel

Post-then-edit a live status line

Open a thread for a long-running rollout


Best Practices

Call send_message(action="list") first when the user hasn’t specified where they want to be notified. Sending to a channel the user doesn’t monitor is noise.
"origin" sends to the chat the conversation started in — usually the right choice. Only override it when you have a specific reason (e.g., sending an ops alert to a dedicated Slack channel).
Include a note like “If send_message returns a ‘No active gateway’ message, skip the notification and continue.” so the agent doesn’t retry endlessly on CLI runs.
Mobile push notifications truncate long text. Send a one-line summary; attach details as a file with MEDIA:.
On channels that support it, edit keeps the chat clean; on channels that don’t, you’ll get unsupported and can post a follow-up.
Adapters reject mutating anyone else’s message with a transport failed.
Never assume ok. On unsupported or failed, fall back to a plain send.

Ask Conversation

Request/reply sibling — message a target and wait for its answer

Send Policy

Restrict which channels send_message is allowed to deliver to

Clarify Tool

Ask the user mid-task for clarifying input

Channels Gateway

Connect agents to Telegram, Slack, Discord, and WhatsApp

Bot Gateway

Run the gateway server

Bot Routing

Route messages by channel and platform

Gateway Threads

Thread-aware routing and creation across platforms

Bot Platform Capabilities

Per-channel supports_edit / supports_delete / supports_reactions matrix