On this page
On this page
CLI commands
Message
openclaw message
Single outbound command for sending messages and channel actions across Discord, Google Chat, iMessage, Matrix, Mattermost (plugin), Microsoft Teams, Signal, Slack, Telegram, and WhatsApp.
Channel selection
--channel <name>is required if more than one channel is configured; with exactly one channel configured, that channel is the default.- Values:
discord|googlechat|imessage|matrix|mattermost|msteams|signal|slack|telegram|whatsapp(Mattermost requires the plugin). - Channel-prefixed targets (for example
discord:channel:123) resolve the owning plugin without an explicit--channel.
With an explicit channel, Gateway-owned actions such as read --channel discord
validate config without running local state migrations. They require a reachable
Gateway. Local actions, broadcasts, dry-runs, and commands that need local channel
discovery retain local config and plugin preparation.
Agent ownership
openclaw message uses the configured
System Agent
as its agent owner, falling back to a retained legacy owner or the sole configured
agent when the System Agent is unset.
In an explicit multi-agent configuration without an owner, the command stops
before sending. Choose an existing agent ID from openclaw agents list, set it
as the System Agent, then retry:
This setting also selects the owner for other ambient system work. The message
command does not accept --agent; --channel and --account select the channel
and channel account.
Target formats (-t, --target)
| Channel | Format |
|---|---|
| Discord | channel:<id>, user:<id>, <@id> mention, or a bare numeric id (treated as a channel id) |
| Google Chat | spaces/<spaceId> or users/<userId> |
| iMessage | handle, chat_id:<id>, chat_guid:<guid>, or chat_identifier:<id> |
| Mattermost (plugin) | channel:<id>, user:<id>, @username, or a bare id (treated as a channel) |
| Matrix | @user:server, !room:server, or #alias:server |
| Microsoft Teams | conversation:<id> (19:...@thread.tacv2), a bare conversation id, or user:<aad-object-id> |
| Signal | +E.164, group:<id>, uuid:<id>, username:<name>/u:<name>, or any of these prefixed with signal: |
| Slack | channel:<id> or user:<id> (a bare id is treated as a channel) |
| Telegram | chat id, @username, or a forum topic target: <chatId>:topic:<topicId> (or --thread-id <topicId>) |
E.164, group JID (...@g.us), or Channel/Newsletter JID (...@newsletter) |
Channel name lookup: for providers with a directory (Discord/Slack/etc), names
like Help or #help resolve via the directory cache, falling back to a live
directory lookup on a cache miss where the provider supports it.
Common flags
Every action accepts: --channel <name>, --account <id>, --json,
--dry-run, --verbose. Actions that take a destination also accept
-t, --target <dest>.
An explicitly empty or whitespace-only --account value is rejected. Omit the
option to use the existing default or bound account, including when a shell
variable is empty. Nonblank account values keep their existing selection rules.
An explicitly empty or whitespace-only --channel value is also rejected. Omit
the option to select the sole configured channel, or use a channel-prefixed
target when supported.
Discord message bodies, captions, poll context, and component text retain leading indentation. Existing empty-message validation still applies. Ordinary message and caption delivery still trims trailing whitespace.
Local message actions run the loaded plugins' shutdown hooks before exiting, including
after an action fails. These hooks have a 2.5-second overall budget and do not change
the action's exit status. message read skips these shutdown hooks. The executable
then drains shared-state database workers within its existing five-second cleanup
budget. If plugin disposal is still pending, it records a deferral and schedules that
drain after disposal settles, preserving state for unfinished writes without making
the command wait again. Cleanup warnings preserve the action's result; they do not
claim the unfinished cleanup completed.
SecretRef resolution
openclaw message resolves channel SecretRefs before running the action,
scoped as narrowly as possible:
- channel-scoped when
--channelis set (or inferred from a prefixed target) - account-scoped when
--accountis also set - all configured channels when neither is set
Unresolved SecretRefs on unrelated channels never block a targeted action; an unresolved SecretRef on the selected channel/account fails the action closed.
Actions
Core
| Action | Channels | Required | Notes |
|---|---|---|---|
send |
Discord, Google Chat, iMessage, Matrix, Mattermost (plugin), Microsoft Teams, Signal, Slack, Telegram, WhatsApp | --target, plus one of --message/--media/--presentation |
See Send below. |
poll |
Discord, Matrix, Microsoft Teams, Telegram, WhatsApp | --target, --poll-question, --poll-option (repeat) |
See Poll below. |
react |
Discord, Matrix, Nextcloud Talk, Signal, Slack, Telegram, WhatsApp | --message-id, --target |
--emoji, --remove (needs --emoji; omit it to clear own reactions where supported, see Reactions). WhatsApp: --participant, --from-me. Signal group reactions require --target-author or --target-author-uuid. Nextcloud Talk only adds reactions; --remove errors. |
reactions |
Discord, Matrix, Microsoft Teams, Slack | --message-id, --target |
--limit. |
read |
Discord, Matrix, Microsoft Teams, Slack | --target |
--limit, --message-id, --before, --after. Discord: --around. Slack: --message-id reads a specific timestamp, combine with --thread-id for an exact thread reply. |
edit |
Discord, Matrix, Microsoft Teams, Slack, Telegram | --message-id, --message, --target |
Telegram forum threads use --thread-id. |
delete |
Discord, Matrix, Microsoft Teams, Slack, Telegram | --message-id, --target |
|
pin / unpin |
Discord, Matrix, Microsoft Teams, Slack | --message-id, --target |
unpin also accepts --pinned-message-id (Microsoft Teams: the pin/list-pins resource id, not the chat message id). |
pins (list) |
Discord, Matrix, Microsoft Teams, Slack | --target |
--limit. |
permissions |
Discord, Matrix | --target |
Matrix: available only when encryption is enabled and verification actions are allowed. |
search |
Discord, Microsoft Teams | --query |
--guild-id (Discord; resolved from --channel-id when omitted), --channel-id (required for Microsoft Teams as Graph <team-id>/<channel-id>), --channel-ids (repeat), --author-id, --author-ids (repeat), --limit. |
member info |
Discord, Matrix, Microsoft Teams, Slack | --user-id |
--channel-id (required for Matrix and Microsoft Teams), --guild-id (Discord). |
Reaction listings show labels, counts, and available users as plain terminal text.
Use --json for the complete channel result.
The legacy message read --include-thread spelling remains accepted for existing
scripts but has no effect.
Member info
Use --channel-id to select a Matrix room or a Microsoft Teams standard channel.
Teams requires the Graph <team-id>/<channel-id> form because the CLI has no
current conversation. Provider access and membership checks still apply.
Send
--media <path-or-url>: attach image/audio/video/document (local path or URL). Repeat to send multiple files in order; Telegram groups consecutive photos into albums.--presentation <json>: shared payload withtext,context,divider,chart,table,buttons, andselectblocks, rendered per channel capability. See Message Presentation.--delivery <json>: generic delivery preferences, for example{"pin": true}.--pinis shorthand for pinned delivery when the channel supports it.--reply-to <id>,--thread-id <id>(Telegram forum topic; Slack thread timestamp, same field as--reply-to).--force-document: preserve original image bytes on Slack, or send images/GIFs/videos as documents on Telegram and WhatsApp, to avoid channel compression.--silent(Telegram, Discord): send without a notification.--gif-playback(WhatsApp only): treat video media as GIF playback.
When a send is suppressed by a message hook, fails, or only partially succeeds,
the command explains the outcome and exits nonzero. Partial delivery keeps any
confirmed message ID. JSON failures include ok: false, deliveryStatus, and
error; successful JSON responses retain their existing shape.
Slack renders supported chart blocks natively; other channels receive the same data as readable text:
Slack also renders explicit table blocks natively. Other channels receive the caption and every row in a fixed text format:
Telegram Mini App buttons use webApp (web_app still parses for legacy
JSON) and only render in private chats between a user and the bot:
Poll
--poll-option <choice>: repeat 2-12 times.--poll-multi: allow multiple selections.- Discord:
--poll-duration-hours,--silent,--message. - Telegram:
--poll-duration-seconds <n>(5-604800; up to seven days),--silent,--poll-anonymous/--poll-public,--thread-id.
Threads
thread create: channels Discord. Required:--thread-name,--target(channel id). Optional:--message-id,--message,--auto-archive-min.thread list: channels Discord. Required:--guild-id. Optional:--channel-id,--include-archived,--before,--limit.thread reply: channels Discord. Required:--target(thread id),--message. Optional:--media,--reply-to.
Emojis
emoji list: Discord (--guild-id), Slack (no extra flags).emoji upload: Discord. Required:--guild-id,--emoji-name,--media. Optional:--role-ids(repeat).
Stickers
sticker send: Discord. Required:--target,--sticker-id(repeat). Optional:--message.sticker upload: Discord. Required:--guild-id,--sticker-name,--sticker-desc,--sticker-tags,--media.
Roles, channels, voice, events (Discord)
role info:--guild-id.role add/role remove:--guild-id,--user-id,--role-id.channel info:--target.channel list:--guild-id.voice status:--guild-id,--user-id.event list:--guild-id.event create: required--guild-id,--event-name,--start-time; optional--end-time,--desc,--channel-id,--location,--event-type,--image <url-or-path>.
Moderation (Discord)
timeout:--guild-id,--user-id; optional--duration-minor--until(omit both to clear the timeout),--reason.kick:--guild-id,--user-id,--reason.ban:--guild-id,--user-id,--delete-days,--reason.
Broadcast
Sends one payload to multiple targets. --targets takes a space-separated
list. Use --channel all to target every configured provider.
If any target fails, is suppressed, or only partially delivers, the broadcast
exits nonzero. Text output identifies failed targets; JSON reports ok: false
and retains every target's result, including failures returned by channel plugins.