A Mattermost bot that relays messages to multiple channels via a guided two-step broadcast wizard. Users interact with the bot exclusively via direct messages.
The bot is built on mmbot_framework, a shared library that handles the WebSocket lifecycle, session management, middleware, and command routing. postbot adds only the broadcast-relay logic on top.
postbot/
main.py # Entry point: load config, build bot, run
bot.py # PostBot(BaseBot) — all command handlers and broadcast wizard
config.py # PostBotConfig(BotConfig) — env var loading and validation
database.py # SQLite broadcast log
patches.py # SSL workaround
channels.json # Channel group definitions (visible groups, private groups, whitelist)
Message flow:
Mattermost WebSocket event
→ IgnoreSelfMiddleware (drop own messages)
→ DMOnlyMiddleware (drop non-DM messages)
→ CommandDispatcher (route to handler by trigger prefix)
→ handler / broadcast wizard (PostBot.on_message)
Create a .env file in the postbot/ directory:
# Required
URL=mattermost.yourdomain.com
TOKEN=your_bot_access_token
TEAM_NAME=your-team-name
# Optional — shown with their defaults
SESSION_TTL_SECONDS=300
SESSION_CLEANUP_INTERVAL_SECONDS=60
# Logging
LOG_LEVEL=INFO # File log level
CONSOLE_LOG_LEVEL=WARNING # Console (stdout) log level — set to INFO to see startup messages
LOG_FILE=logs/bot.log
# Postbot-specific
BOT_LOG_CHANNEL_ID= # Channel ID for broadcast audit messages (empty = disabled)
CHANNELS_JSON_PATH=channels.json
DB_PATH=broadcast_log.dbTip: Set
CONSOLE_LOGGING_LEVEL=INFOto see WebSocket connection and startup messages in the terminal.
Defines the channel groups the bot can broadcast to. Loaded at startup and extendable at runtime via !add_group / !add_private_group.
whitelist = [ "14d9s71is3fh3duwj9a6u9k4jr",]
[groups]
"name1" = [ "id1", "id2",]
"name2" = [ "id3", "id4", "id5",]
[private_groups]
"name3" = [ "id6", "id7", "id8",]
- groups — visible to all users via
!get_groups - private_groups — visible only via
!get_private_groups - whitelist — channel IDs that may be targeted individually by name or ID, outside of groups
Requires Python 3.14+ and uv.
cd postbot
uv sync
# create and fill in .env (see Configuration section above)
uv run main.pycd postbot
uv run pytest -vcd postbot
podman build -f Containerfile -t mailman-bot .
podman run --env-file .env -v ./channels.json:/app/channels.json:ro mailman-botAll interaction happens via direct message to the bot. Messages in channels or group chats are ignored.
| Command | Description |
|---|---|
!help / help / --help / man |
Show usage instructions |
!channels |
List all channels the bot has access to in the current team |
!id <channel name> |
Look up a channel's ID by name |
!get_groups |
List all visible channel groups |
!get_private_groups |
List all private channel groups |
!add_group <TOML> |
Add one or more public groups at runtime |
!add_private_group <TOML> |
Add one or more private groups at runtime |
| (any other message) | Start the broadcast wizard |
Sending any message that isn't a command starts a two-step broadcast:
-
Target selection — the bot asks which channels or groups to send to. Accepted formats:
- Group names (from
!get_groups/!get_private_groups) - Individual channel names (must be on the whitelist)
- Channel IDs directly
- Comma-separated combinations:
Group A, Group B, some-channel-name
- Group names (from
-
Confirmation — the bot shows a preview of recipients. Reply
yesto send,noto cancel.
Files attached to your original message are relayed alongside the text.
Sessions expire after SESSION_TTL_SECONDS (default 5 minutes). If your session expires before you confirm, the bot sends a DM notification.
!add_group {"New Group Name": ["channel_id_1", "channel_id_2"]}
Multiple groups in one command:
!add_group {"Floor 1": ["id_a", "id_b"], "Floor 2": ["id_c", "id_d"]}
Changes are persisted to channels.json immediately.
Every successful broadcast is recorded in a SQLite database (broadcast_log.db by default), including sender, timestamp, target channels, and message content.
If BOT_LOG_CHANNEL_ID is set, a summary is also posted to that channel after each broadcast.