Mattermost
Self-hosted Mattermost with bot-token outbound + outgoing-webhook inbound. Public channels only on the inbound side.
🟡 Outbound-ready, inbound only on public channels via outgoing webhook, the lightest-friction path. Mattermost's WebSocket bot mode (which supports DMs and private channels) is a future enhancement.
What you get
- Outbound text via
POST /api/v4/postswith bot personal access token. Threaded replies viaroot_idare supported. - Inbound outgoing webhook at
/webhooks/mattermost. Mattermost POSTs to the webhook URL when a configured trigger word appears in a public channel. - Text only this PR. File uploads via Mattermost's Files API and attachments parsing are deferred.
- 16383-char text limit (Mattermost's default
PostMessageMaxRunes).
Setup
1. Create a bot account
- Sign in to Mattermost as a System Admin
- System Console → "Integrations" → "Bot Accounts": "Add Bot Account"
- Pick a username (e.g.
minara), display name, optional avatar. On Save, Mattermost reveals the Bot Personal Access Token. Save it (you cannot retrieve it later) - Invite the bot into the target channel:
/invite @minarain the channel composer
2. Set up an outgoing webhook (optional, for inbound)
- Same screen, "Integrations" → "Outgoing Webhooks": "Add Outgoing Webhook"
- Configure:
- Channel: the public channel the webhook listens in
- Trigger words: e.g.
@bot,!minara(Mattermost only fires the webhook when a message starts with one of these) - Callback URLs:
https://<your-host>/webhooks/mattermost
- Save. Copy the generated Token (becomes
MATTERMOST_OUTGOING_WEBHOOK_TOKEN)
3. Configure Minara
minara auth messaging add
# pick `mattermost` from the list.Or set env vars:
MATTERMOST_URL=https://mattermost.example.com
MATTERMOST_BOT_TOKEN=<personal access token>
MATTERMOST_DEFAULT_CHANNEL_ID=<channel id from Channel URL>
MATTERMOST_OUTGOING_WEBHOOK_TOKEN=<outgoing-webhook token>The channel id appears in the URL when you open a channel:
https://mattermost.example.com/team/channels/<id> → the <id>
slug.
4. Test
minara auth messaging test mattermostInbound webhook
Mattermost's outgoing webhook POSTs application/x-www-form-urlencoded
to /webhooks/mattermost with fields including:
token=<webhook token>
channel_id=<channel id>
user_id=<user id>
user_name=<username>
text=<the user's message>
post_id=<message id>
trigger_word=<matched trigger>
from_webhook=falseMinara verifies the token field against MATTERMOST_OUTGOING_WEBHOOK_TOKEN
in constant time. The from_webhook === "true" flag filters
self-loops (the bot's own posts re-arriving via the trigger).
Limits & caveats
- Outgoing webhooks only fire in PUBLIC channels. Private channels and DMs need the WebSocket bot path (planned). Configure trigger words carefully so a hot channel does not flood the agent.
- Trigger-word prefix only. Mattermost matches the trigger at the
start of the message.
@bot helloworks,hello @botdoes not. - Constant-time compare, not HMAC. The token is the entire authentication surface for inbound. Treat it as a secret.
- No streaming edits. Mattermost's
PUT /posts/<id>exists but the gateway does not wire it.
Troubleshooting
"401 Unauthorized" on outbound
MATTERMOST_BOT_TOKENis wrong, expired, or scoped to a different team. Generate a fresh token in System Console and update env.
"Outgoing webhook does not fire"
- The channel is private (only public is supported)
- The message did not start with one of the trigger words
- The webhook is configured on a different channel than the message
"401 on inbound"
MATTERMOST_OUTGOING_WEBHOOK_TOKENmismatch. The token rotates if you re-create the webhook; sync env.
Reference
- Env vars:
MATTERMOST_* - Outbound:
apps/agent/src/messaging/mattermost.ts - Inbound spec:
apps/agent/src/messaging/inbound/specs/mattermost.ts - Mattermost integrations guide: docs.mattermost.com / Outgoing Webhooks