docs/book/src/channels/mattermost.md
REST v4 polling client. Self-hosted, on-prem, or sovereign-cloud Mattermost servers all work the same way: the bot polls the channels it can read every 3 seconds for new posts, and reply posts go out via POST /api/v4/posts.
{{#peer-group mattermost}}
To allowlist a specific human, copy their user ID from System Console → User Management. Mattermost matches the user UUID, not a username, and does not resolve usernames at message-receive time.
Configure a Mattermost channel (url plus a bot_token secret, see Authentication) through one of the surfaces below. That alone gives you:
mention_only bypassed inside DM and group-DM channels (so 1:1 conversations don't need the bot to be @-mentioned).To restrict the bot, narrow with channel_ids, team_ids, or discover_dms.
bot_token and password are secrets:
{{#secret-config channels.mattermost.<alias>.bot_token}}
{{#config-fields channels.mattermost}}
There are two scoping modes.
channel_ids is empty or ["*"]). On startup and every 60 seconds thereafter, the bot calls GET /api/v4/users/me/channels, filters the result by team_ids (public/private channels) and discover_dms (DMs/group DMs), and polls each surviving channel. New DMs created mid-runtime appear at the next refresh.channel_ids is a non-empty list of IDs other than *). On startup the bot calls GET /api/v4/channels/{id} for each entry to learn its type (so it knows which are DMs for the mention_only bypass), then polls exactly those channels forever. No periodic re-discovery.In both modes each channel has its own since cursor: the bot tracks the highest create_at it has processed per channel and passes that as since=<ms> on the next GET /api/v4/channels/{id}/posts call. Cursors do not leak across channels, so a slow-moving channel doesn't suppress posts on a busy one.
Mattermost classifies channels by type:
type | meaning |
|---|---|
O | Public team channel. |
P | Private team channel. |
G | Group direct message (multi-user DM). |
D | Direct message (1:1). |
G and D are treated identically by ZeroClaw: both carry no team_id, both are gated by discover_dms, and both implicitly bypass mention_only (a private conversation has no ambient noise to filter against).
Authorization for DM senders still goes through the channel's peer-group resolver, same as any other channel. discover_dms is a knob, not a security boundary; peer groups decide who is allowed to address the agent.
root_id is set) → the reply always lands in that thread, regardless of thread_replies.thread_replies = true (default) → the reply opens a thread rooted on the inbound post.thread_replies = false → the reply is posted at channel root.{{#thread-context channel="Mattermost" prop="thread_replies" path="channels.mattermost.<alias>.thread_replies"}}
Two paths:
bot_token. Tokens survive password rotations and are easier to revoke.login_id (email or username) and password. The bot calls POST /api/v4/users/login on startup and caches the returned session token in memory. No persistence to disk.bot_token wins when both are set.
When [transcription] is configured and an inbound post has an audio attachment (mime audio/* or extension ogg/mp3/m4a/wav/opus/flac) with no text body, the audio is downloaded via GET /api/v4/files/{file_id} and routed through the configured transcription provider. The transcript is prefixed [Voice] and becomes the message content. Attachments larger than 25 MB or longer than transcription.max_duration_secs are dropped with a WARN.
zeroclaw), enable the scopes you want.mattermost.<alias> channel referencing the token through the gateway, zerocode, or zeroclaw config set.[agents.<alias>] via channels = ["mattermost.<alias>"].channel_ids or team_ids.GET /api/v4/users/me and cached for the process lifetime. Username changes require a restart.