Skip to content

Repository files navigation

mm

A full-featured command-line client for Mattermost.

mm lets you interact with your Mattermost server entirely from the terminal: send messages, manage channels, stream real-time notifications, upload files, schedule posts, and much more.

Features

  • Multi-server profiles with token and password authentication, including a per-profile read-only mode that blocks all mutations
  • Real-time notifications via WebSocket with event and channel filtering
  • Full post lifecycle: create, edit, delete, pin, react, search, threads, reminders
  • Channel management: join, leave, create, archive, read, favorite, notify settings
  • Direct messages and group chats
  • Thread operations: list, follow/unfollow, mark read/unread
  • File upload/download/search
  • Drafts and scheduled posts with flexible time parsing
  • Channel bookmarks, saved/flagged posts, preferences
  • User operations: status, avatar, typing indicator, autocomplete
  • Session and access token management
  • Custom emoji, webhooks, bots, groups, slash commands, plugins
  • Local archive of every channel you can read, synced incrementally and searchable offline
  • JSON output (--json) for scripting and piping
  • Shell completion for bash, zsh, fish, and PowerShell
  • Static binary with zero CGO dependencies

Installation

From source

git clone https://github.com/ziyan/mm.git
cd mm
make build

This produces a statically linked binary ./mm. Copy it to your $PATH:

sudo cp mm /usr/local/bin/

Requirements

  • Go 1.25+

Quick start

Login

# Login with a personal access token (recommended)
mm auth login --url https://mattermost.example.com --token YOUR_TOKEN

# Login with username and password
mm auth login --url https://mattermost.example.com --user alice --password secret

Set your active team

mm team list
mm team switch my-team

Send a message

mm post create town-square "Hello from the CLI!"

Read messages

mm post list town-square
mm post list town-square -n 50  # last 50 messages

Stream real-time notifications

mm notify                          # all events
mm notify --event posted           # only new messages
mm notify --channel town-square    # only one channel
mm notify --json                   # JSON output for scripting

Usage

Global flags

--json               Output in JSON format
--token string       Override access token
--server string      Override server URL
-T, --team string    Override active team (by name)
-l, --log-level      Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: WARNING)

Authentication

mm auth login --url URL --token TOKEN   # login with token
mm auth login --url URL -u USER -p PASS # login with password
mm auth login --url URL ... --readonly  # mark new profile as read-only
mm auth status                          # show current profile
mm auth list                            # list all profiles
mm auth switch <profile>                # switch active profile
mm auth remove <profile>                # remove a profile
mm auth set-readonly <profile> on|off   # toggle read-only mode on a profile
mm auth sessions                        # list active sessions
mm auth revoke-session <session-id>     # revoke a session
mm auth revoke-all                      # revoke all sessions
mm auth token-create <description>      # create personal access token
mm auth token-list                      # list your tokens
mm auth token-revoke <token-id>         # revoke a token

Teams

mm team list                  # list your teams
mm team switch <name>         # set active team
mm team info [name]           # show team details
mm team members [name]        # list team members
mm team invite user@email.com # invite by email

Channels

mm channel list                         # list joined channels
mm channel list --all                   # include unjoined channels
mm channel join <name>                  # join a channel
mm channel leave <name>                 # leave a channel
mm channel create <name>                # create public channel
mm channel create <name> --private      # create private channel
mm channel info <name>                  # show channel details
mm channel members <name>               # list members
mm channel archive <name>               # archive a channel
mm channel unread                       # list channels with unread messages
mm channel read <name>                  # mark as read
mm channel favorite <name>              # add to favorites
mm channel unfavorite <name>            # remove from favorites
mm channel notify <name>                # show notification settings
mm channel notify <name> --desktop all  # set desktop notifications
mm channel categories                   # list sidebar categories

Posts / messages

mm post create <channel> <message>             # post a message
mm post create <channel> <msg> -f file.png     # post with attachment
mm post create <channel> <msg> --root-id ID    # reply in thread
mm post list <channel>                         # list recent messages (default: 20)
mm post list <channel> -n 50                   # last 50 messages
mm post list <channel> --since 24h             # posts in the last 24 hours
mm post list <channel> --since 2026-03-29      # posts since a date
mm post list <channel> --user alice            # only posts by alice
mm post list <channel> --threads               # inline thread replies
mm post list <channel> --threads --user alice  # alice's posts and replies
mm post list <channel> --collapse-threads      # roots only with reply counts
mm post list <channel> --full-id               # show full 26-char post IDs
# Note: --threads and --collapse-threads are mutually exclusive.
# --count (-n) and --since cannot be combined (--since returns all matching posts).
# --threads is not supported with --json; use --collapse-threads instead.
mm post thread <post-id>                       # view a thread
mm post reply <post-id> <message>              # reply to a thread
mm post edit <post-id> <new-message>           # edit a post
mm post delete <post-id>                       # delete a post
mm post pin <post-id>                          # pin a post
mm post unpin <post-id>                        # unpin a post
mm post react <post-id> thumbsup              # add reaction
mm post unreact <post-id> thumbsup            # remove reaction
mm post search <query>                         # search posts
mm post search <query> --or                    # OR search
mm post pinned <channel>                       # list pinned posts
mm post history <post-id>                      # show edit history
mm post remind <post-id> 1h                    # set reminder

Direct messages

mm dm send <username> [message]                 # send a DM
echo "hello" | mm dm send <username>           # pipe message from stdin
mm dm send <username> [message] -f file.png     # DM with attachment (message optional)
mm dm read <username>                           # read DM history
mm dm read <username> -n 50                     # last 50 messages
mm dm list                                      # list DM conversations
mm dm group user1,user2 <message>               # send group message
mm dm group user1,user2 -f report.pdf           # group message with attachment

Threads

mm thread list                   # list your threads
mm thread list --unread          # only unread threads
mm thread view <thread-id>       # view a thread
mm thread follow <thread-id>     # follow a thread
mm thread unfollow <thread-id>   # unfollow a thread
mm thread read <thread-id>       # mark as read
mm thread unread <post-id>       # mark as unread
mm thread read-all               # mark all threads as read

Drafts

mm draft list                              # list your drafts
mm draft create <channel> <message>        # create/update a draft
mm draft delete <channel>                  # delete a draft

Scheduled posts

mm scheduled list                                       # list scheduled posts
mm scheduled create <channel> 1h30m "reminder message"  # schedule by duration
mm scheduled create <channel> 14:30 "afternoon msg"     # schedule by time today
mm scheduled create <channel> 2025-12-01T09:00 "msg"    # schedule by datetime
mm scheduled delete <id>                                # delete scheduled post

Files

mm file upload <channel> file1.png file2.pdf   # upload files
mm file upload <channel> doc.pdf -m "check this out"
mm file download <file-id>                     # download to current dir
mm file download <file-id> output.pdf          # download to specific path
mm file download <file-id> -                   # download to stdout
mm file info <file-id>                         # show file info
mm file search <query>                         # search files

Archive

Keep a local copy of the channels you can read, and search it without going back to the server. A sync is incremental: the first run of a channel reads it in full, later runs ask only for posts newer than the last one archived, so re-running is cheap and safe.

mm archive sync ~/mattermost-archive              # fetch what is new
mm archive sync ~/mattermost-archive --channels all   # public channels too, not only yours
mm archive sync ~/mattermost-archive --files mine     # also download your own attachments
mm archive sync ~/mattermost-archive --files all --max-file-mb 5  # everyone's, skipping the big ones
mm archive sync ~/mattermost-archive --skip-posts --files mine    # attachments only, for posts already archived
mm archive sync ~/mattermost-archive --only general-chat   # just the channels whose name matches
mm archive sync ~/mattermost-archive --full           # ignore the high-water marks, re-read everything
mm archive sync ~/mattermost-archive --since 2026-08-01  # re-read back to a date and merge, to fill a gap
mm archive sync ~/mattermost-archive --workers 16     # read more channels at once
mm archive sync ~/mattermost-archive --exclude noisy- # skip some channels, just this once

mm archive exclude ~/mattermost-archive           # what a sync leaves alone
mm archive exclude ~/mattermost-archive noisy-alerts   # and stop reading this one
mm archive exclude ~/mattermost-archive noisy-alerts --remove

mm archive status ~/mattermost-archive            # what the archive holds
mm archive reindex ~/mattermost-archive           # build the search index for an older archive
mm archive search ~/mattermost-archive "deadlock" # search it offline
mm archive search ~/mattermost-archive "timeout" -c general-chat -u alice --since 2026-01-01
mm archive search ~/mattermost-archive "timeout" --team platform   # only teams whose name matches
mm archive search ~/mattermost-archive "c.t sat" --regex --limit 10
mm archive search ~/mattermost-archive "deadlock" --json

Each match prints when and where it was written, who wrote it, and a permalink back to the thread, which is what makes it useful to paste into a conversation.

The layout under the archive directory is plain files, so anything can read it:

channels.json                  every channel seen, with its team, type and delete_at
users.json                     every user seen, so a search can print names offline
me.json                        the authenticated user
state.json                     per-channel high-water mark, for the next sync
posts/<team>/<channel>.jsonl   one post per line, oldest first, as the server sent it
index/<team>/<channel>.jsonl   the fields a search reads, one line per post
excluded.json                  channels a sync leaves alone, if any
posts/direct/<username>.jsonl  direct messages, one file per person
posts/group/<usernames>.jsonl  group messages, named after the people in them
files.jsonl                    one record per attachment referenced by an archived post
files/<fileId>__<name>         attachment contents

Posts are stored exactly as the server sent them rather than re-encoded, so a field this version of mm does not know about is still there for a later one.

A post as the server sends it is mostly metadata: link previews, reactions, file details, properties. On a large archive the message text is a few percent of the bytes. So beside each channel's posts a sync keeps a compact index holding only what a search reads, and search and status read that instead. The posts stay the source of truth: each index records the size of the posts file it was built from, and one that no longer matches is ignored in favor of the posts. An index can make a search slower when it is missing or stale, never make it miss anything. mm archive reindex builds indexes for an archive written before they existed.

A sync reads a channel by paging it newest first until it reaches what is already archived. It does not enumerate with the since parameter: that response is capped at about a thousand posts and is ordered by when a post was last updated rather than when it was written, so the newest post it returns can sit far ahead of posts it never carried, and a mark advanced to that value steps over them for good. since is still asked first, as a cheap question of whether a channel has anything new at all, because a post written after the mark was also updated after it.

--since is the repair: it re-reads every channel back to the date given and merges what it finds with what is on disk, keeping both, so a gap an older sync left is filled without disturbing anything already archived. An ordinary sync cannot do this, because it never looks behind its own high-water mark.

A sync is almost entirely waiting on the network: every request is a round trip, and a channel costs one to ask whether it has anything new plus one for every 200 posts it has. It therefore reads --workers channels at once, four by default. Attachments go through the same pool rather than waiting for the posts to finish, so a sync that ends with a couple of enormous channels still being paged spends the rest of its workers downloading instead of idling. Raising the count helps in proportion until the server becomes the limit, so 16 is reasonable against a server that is not busy.

A channel can be more than it is worth reading. Paging is by offset, which the server answers out of a database OFFSET, so the cost of a page grows with how deep it is: on one bot channel of a million posts, a page near the start took 1.3 seconds and a page near the end took 26. mm archive exclude <dir> <pattern> records such a channel in excluded.json, and every later sync of that archive skips it. A pattern is a channel id, or any part of a channel name. Posts already archived from an excluded channel stay where they are.

Paging is by offset over a live channel, so a post written while a sync is walking that channel can shift the window and be missed. A later sync does not look behind its mark, so --since is what recovers one. A first read also holds the channel in memory, so it costs about what that channel's file costs on disk.

Deleted posts are the one thing a sync does not archive. Paging omits them, and include_deleted needs system admin, so a post deleted after it was written is kept only if a sync saw it while it was still there.

A channel keeps the team and name it was first archived under, so a channel renamed on the server, or a person who changes their username, does not start a second file beside the first.

Two notes on what a sync can and cannot reach. Channels archived while you were a member are included; they are invisible to the ordinary channel listing and need include_deleted, which the sync passes. Public channels you have left are reachable with --channels public, because a public channel can be read without joining it. Private channels you have left are gone: reading one requires membership, and the membership row is deleted with it.

Bookmarks

mm bookmark list <channel>                          # list bookmarks
mm bookmark add <channel> "Docs" https://example.com  # add bookmark
mm bookmark delete <channel> <bookmark-id>          # delete bookmark

Saved / flagged posts

mm saved list                        # list saved posts
mm saved list --channel town-square  # filter by channel
mm saved add <post-id>               # save a post
mm saved remove <post-id>            # unsave a post

User operations

mm user me                          # show your profile
mm user info <username>             # show user profile
mm user status                      # show your status
mm user status online               # set status (online/away/dnd/offline)
mm user status --message "In a meeting" --emoji calendar
mm user search <query>              # search users
mm user list                        # list users in team
mm user autocomplete <prefix>       # autocomplete usernames
mm user typing <channel>            # send typing indicator
mm user avatar get [username]       # download profile image
mm user avatar set <image-file>     # set profile image
mm user avatar reset                # reset to default

Preferences

mm preference list                            # list all preferences
mm preference list display_settings           # list by category
mm preference set <category> <name> <value>   # set a preference
mm preference delete <category> <name>        # delete a preference

Emoji

mm emoji list                        # list custom emoji
mm emoji create <name> <image-file>  # create custom emoji
mm emoji delete <name>               # delete custom emoji
mm emoji search <query>              # search emoji

Webhooks

mm webhook list-incoming                              # list incoming webhooks
mm webhook list-outgoing                              # list outgoing webhooks
mm webhook create-incoming <channel> --display-name X # create incoming
mm webhook create-outgoing <channel> --display-name X --url https://...
mm webhook delete <id>                                # delete incoming
mm webhook delete <id> --outgoing                     # delete outgoing

Bots

mm bot list                                  # list bots
mm bot create <username>                     # create a bot
mm bot create <username> --display-name Bot  # with display name
mm bot info <bot-id>                         # show bot details
mm bot disable <bot-id>                      # disable a bot
mm bot enable <bot-id>                       # enable a bot

Groups

mm group list                       # list groups
mm group list --channel <name>      # groups in a channel
mm group members <group-id>         # list group members
mm group info <group-id>            # show group details

Slash commands and plugins

mm slash exec <channel> /giphy cats  # execute a slash command
mm slash list                        # list custom commands
mm plugin list                       # list installed plugins

Server

mm server ping    # check connectivity
mm server info    # show server version and details

Shell completion

# Bash
mm completion bash > /etc/bash_completion.d/mm

# Zsh
mm completion zsh > "${fpath[1]}/_mm"

# Fish
mm completion fish > ~/.config/fish/completions/mm.fish

JSON output

All commands support --json for machine-readable output:

mm channel list --json | jq '.[].name'
mm post list town-square --json | jq '.order[]'
mm user me --json | jq '.username'

Multiple servers

mm auth login --url https://server1.com --token TOKEN1 --name work
mm auth login --url https://server2.com --token TOKEN2 --name personal
mm auth list
mm auth switch personal

Configuration

Configuration is stored in ~/.config/mm/config.json (or $XDG_CONFIG_HOME/mm/config.json). The file contains server profiles with authentication tokens.

API coverage

Supported

The following Mattermost REST API (v4) endpoint groups are fully supported:

API group CLI commands Operations
Users user Get self, get by username, search, list, autocomplete, status get/set, custom status, profile image get/set/reset, typing indicator
Teams team List user teams, get by name, members, invite by email, switch active team
Channels channel List, join, leave, create (public/private), info, members, archive, unread, mark read, notification settings, favorite/unfavorite, sidebar categories
Posts post Create (with file attachments), list, get thread, reply, edit (patch), delete, pin/unpin, reactions add/remove, search, pinned posts, edit history, reminders
Direct messages dm Send DM, read DM history, list conversations, create group messages
Threads thread List user threads, view, follow/unfollow, mark read/unread, mark all read
Files file Upload (with post), download, info, search
Drafts draft List, create/update (upsert), delete
Scheduled posts scheduled List, create (with duration/datetime parsing), delete
Channel bookmarks bookmark List, add (link type), delete
Saved/flagged posts saved List (with channel filter), save, unsave
Preferences preference List (all or by category), set, delete
Emoji emoji List, create, delete, search/autocomplete
Webhooks webhook List/create/delete incoming, list/create/delete outgoing
Bots bot List, create, info, enable, disable
Groups group List (all or by channel), members, info
Slash commands slash Execute, list custom commands
Plugins plugin List installed (active/inactive)
Sessions auth List sessions, revoke session, revoke all
Access tokens auth Create, list, revoke personal access tokens
Server server Ping with status, client config/server info
WebSocket notify Real-time event streaming with event type and channel filters
Archive archive Incremental local archive of posts and attachments, offline search, status
Authentication auth Login (token or password), multi-profile management

Not supported

The following API groups are not exposed in the CLI. These are primarily server administration, enterprise, or system-level endpoints that are not relevant to day-to-day user workflows:

API group Reason
Server configuration (GetConfig, UpdateConfig, PatchConfig, ReloadConfig) Admin-only, dangerous to expose in a user CLI
LDAP (sync, test, groups, certificates, migration) Enterprise admin feature
SAML (certificates, metadata, migration) Enterprise admin feature
Compliance (reports, exports) Enterprise compliance officer feature
Data retention (policies, channels, teams) Enterprise admin feature
Elasticsearch / Bleve (purge, test, indexing) Admin search engine management
Cluster (status) Admin high-availability management
License (upload, remove, get) Admin licensing
OAuth apps (CreateOAuthApp, GetOAuthApps, DeleteOAuthApp) Developer/admin app registration
Outgoing OAuth connections Enterprise integration admin feature
IP filters Enterprise network admin feature
Access control policies Enterprise admin feature
Content flagging / moderation Enterprise content moderation
Exports / Imports (bulk data) Admin data migration
Jobs (create, get, cancel, download) Admin background job management
Logs (GetLogs, PostLog) Admin server log access
Analytics (GetAnalyticsOld) Admin analytics/reporting
Audits (GetAudits) Admin audit trail
Plugin management (install, upload, enable, disable, remove) Admin plugin lifecycle
Scheme management (create, get, patch, delete) Admin permission schemes
Role management (create, get, patch) Admin role/permission management
User admin (create, delete, deactivate, promote/demote, update auth, MFA, password reset) Admin user management
Team admin (create, delete, update, privacy, scheme) Admin team management
Channel admin (convert, move, update scheme, privacy, moderation) Admin channel management
Remote clusters / shared channels Enterprise federation features
Cloud (customer, products, subscription, invoices) Mattermost Cloud admin
Notices (product notices) System notice management
Marketplace (list, install marketplace plugins) Admin plugin marketplace
Integrity check (CheckIntegrity) Admin data integrity
Brand image (upload, get, delete) Admin branding
Terms of service Admin legal/compliance
Reports (user reporting) Admin reporting

Contributing

See CONTRIBUTING.md for development setup, coding conventions, and guidelines.

License

MIT

About

Mattermost CLI

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages