Mattermost
Communication Tool
Mattermost
Overview
Mattermost is a self-hostable open-source messaging platform. With a Slack-like UI/UX while being completely operable in your own environment, it's ideal for enterprises prioritizing data sovereignty and security. It offers rich plugin systems, bot development capabilities, and RESTful APIs for advanced customization.
Details
Mattermost is an open-source enterprise messaging platform released in 2016. Designed as a Slack alternative, it's experiencing growing adoption especially among enterprises and organizations that prioritize security and privacy. With rich integrations with DevOps tools including GitLab, it offers features specialized for development teams.
In 2024-2025, significant feature enhancements were made including Playbooks v2 introduction, AI/LLM integration with Agents plugin, migration from Docker Content Trust to Sigstore Cosign, and addition of custom profile attribute fields. The plugin system built with Go and React provides powerful extensibility to modify server, web, and desktop app behavior.
Mattermost's REST API complies with OpenAPI specifications, making bot development and custom integration construction easy. Self-hosted environments support unlimited users with advanced enterprise security and management features. You can install pre-built plugins from Mattermost Marketplace or develop your own plugins.
Pros and Cons
Pros
- Complete Self-Hosting: Own environment operation, data sovereignty assurance
- Open Source: Source code availability, high customization freedom
- Rich Plugin System: Powerful extensions with Go + React
- DevOps Integration: Rich connectivity with GitHub, GitLab, Jenkins, etc.
- Enterprise Security: LDAP/SAML, multi-factor authentication, audit logs
- Unlimited Users: No restrictions in self-hosted environments
- Bot & API Features: RESTful API, Interactive Bot, Webhook support
- AI Integration: Multiple LLM support through Agents plugin
Cons
- Operational Costs: Need for server construction, maintenance, and management
- Learning Curve: Technical requirements for setup and customization
- Cloud Version Limitations: Custom plugin restrictions in Mattermost Cloud
- UI/UX: Usability challenges compared to Slack
- Mobile Apps: Native app feature limitations
- Marketplace: Plugin ecosystem scale
Key Links
- Mattermost Official Site
- Mattermost Developer Portal
- Mattermost API Reference
- Plugin Development Guide
- GitHub Repository
- Mattermost Marketplace
- Documentation
Code Examples
Basic Mattermost Plugin Development Structure
// server/main.go - Plugin main entry point
package main
import (
"fmt"
"encoding/json"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/model"
)
type Plugin struct {
plugin.MattermostPlugin
configuration *configuration
}
type configuration struct {
Enabled bool `json:"enabled"`
APIKey string `json:"api_key"`
}
// OnActivate called when plugin is activated
func (p *Plugin) OnActivate() error {
p.API.LogInfo("Plugin activated")
// Register slash command
if err := p.API.RegisterCommand(&model.Command{
Trigger: "hello",
DisplayName: "Hello World",
Description: "Execute Hello World command",
AutoComplete: true,
AutoCompleteDesc: "Send Hello World message",
}); err != nil {
return fmt.Errorf("command registration error: %w", err)
}
return nil
}
// ExecuteCommand called when slash command is executed
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
switch args.Command {
case "/hello":
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
Text: "Hello, World! Sent from plugin.",
}, nil
default:
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
Text: "Unknown command.",
}, nil
}
}
// MessageWillBePosted called before message is posted
func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
// Process messages containing specific keywords
if contains(post.Message, "emergency") {
// Notify administrators for emergency messages
p.sendAdminNotification(post)
}
return post, ""
}
func (p *Plugin) sendAdminNotification(originalPost *model.Post) {
adminPost := &model.Post{
UserId: p.botUserID,
ChannelId: p.adminChannelID,
Message: fmt.Sprintf("🚨 Emergency message posted: %s", originalPost.Message),
}
if _, err := p.API.CreatePost(adminPost); err != nil {
p.API.LogError("Failed to send admin notification", "error", err.Error())
}
}
func main() {
plugin.ClientMain(&Plugin{})
}
Webhook Integration Implementation
// webhook.go - Webhook processing implementation
package main
import (
"encoding/json"
"net/http"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/model"
)
type WebhookPayload struct {
EventType string `json:"event_type"`
Data interface{} `json:"data"`
}
// InitAPI HTTP route initialization
func (p *Plugin) InitAPI() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/webhook", p.handleWebhook).Methods("POST")
r.HandleFunc("/health", p.handleHealth).Methods("GET")
return r
}
func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
var payload WebhookPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Invalid JSON payload", http.StatusBadRequest)
return
}
switch payload.EventType {
case "deployment.success":
p.handleDeploymentSuccess(payload.Data)
case "build.failed":
p.handleBuildFailed(payload.Data)
case "security.alert":
p.handleSecurityAlert(payload.Data)
default:
p.API.LogInfo("Unhandled webhook event", "event_type", payload.EventType)
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "success"}`))
}
func (p *Plugin) handleDeploymentSuccess(data interface{}) {
deploymentData := data.(map[string]interface{})
post := &model.Post{
UserId: p.botUserID,
ChannelId: p.deploymentChannelID,
Message: fmt.Sprintf("✅ Deployment successful: %s", deploymentData["service"]),
Props: map[string]interface{}{
"attachments": []map[string]interface{}{
{
"color": "#28a745",
"title": "Deployment Details",
"fields": []map[string]interface{}{
{"title": "Service", "value": deploymentData["service"], "short": true},
{"title": "Version", "value": deploymentData["version"], "short": true},
{"title": "Environment", "value": deploymentData["environment"], "short": true},
},
},
},
},
}
if _, err := p.API.CreatePost(post); err != nil {
p.API.LogError("Post creation error", "error", err.Error())
}
}
Interactive Dialog Implementation
// dialog.go - Interactive dialog implementation
func (p *Plugin) openTaskCreationDialog(userID, triggerId string) {
dialog := model.OpenDialogRequest{
TriggerId: triggerId,
URL: fmt.Sprintf("%s/plugins/%s/dialog", p.GetSiteURL(), manifest.Id),
Dialog: model.Dialog{
CallbackId: "create_task_dialog",
Title: "Create New Task",
IconURL: p.GetBundlePath() + "/assets/icon.png",
SubmitLabel: "Create",
NotifyOnCancel: true,
Elements: []model.DialogElement{
{
DisplayName: "Task Name",
Name: "task_title",
Type: "text",
SubType: "text",
MaxLength: 100,
Placeholder: "Enter task name",
},
{
DisplayName: "Description",
Name: "task_description",
Type: "textarea",
MaxLength: 500,
Placeholder: "Enter task details",
Optional: true,
},
{
DisplayName: "Priority",
Name: "priority",
Type: "select",
Options: []*model.PostActionOptions{
{Text: "Low", Value: "low"},
{Text: "Medium", Value: "medium"},
{Text: "High", Value: "high"},
{Text: "Urgent", Value: "urgent"},
},
},
{
DisplayName: "Due Date",
Name: "due_date",
Type: "text",
SubType: "date",
Optional: true,
},
},
},
}
if err := p.API.OpenInteractiveDialog(dialog); err != nil {
p.API.LogError("Dialog open error", "error", err.Error())
}
}
// Dialog submission handling
func (p *Plugin) handleDialogSubmission(w http.ResponseWriter, r *http.Request) {
var request model.SubmitDialogRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if request.CallbackId == "create_task_dialog" {
task := Task{
Title: request.Submission["task_title"].(string),
Description: request.Submission["task_description"].(string),
Priority: request.Submission["priority"].(string),
DueDate: request.Submission["due_date"].(string),
CreatedBy: request.UserId,
CreatedAt: time.Now(),
}
if err := p.createTask(task); err != nil {
response := model.SubmitDialogResponse{
Error: "Failed to create task",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
return
}
// Success response
w.WriteHeader(http.StatusOK)
}
}
Bot API Message Processing
// Node.js Mattermost Bot implementation
const axios = require('axios');
class MattermostBot {
constructor(serverUrl, botToken) {
this.serverUrl = serverUrl;
this.botToken = botToken;
this.headers = {
'Authorization': `Bearer ${botToken}`,
'Content-Type': 'application/json'
};
}
// Send message
async sendMessage(channelId, message, props = {}) {
try {
const response = await axios.post(
`${this.serverUrl}/api/v4/posts`,
{
channel_id: channelId,
message: message,
props: props
},
{ headers: this.headers }
);
return response.data;
} catch (error) {
console.error('Message send error:', error.response.data);
throw error;
}
}
// File upload
async uploadFile(channelId, filePath, filename) {
const FormData = require('form-data');
const fs = require('fs');
const form = new FormData();
form.append('files', fs.createReadStream(filePath), filename);
form.append('channel_id', channelId);
try {
const response = await axios.post(
`${this.serverUrl}/api/v4/files`,
form,
{
headers: {
...this.headers,
...form.getHeaders()
}
}
);
return response.data;
} catch (error) {
console.error('File upload error:', error.response.data);
throw error;
}
}
// Get channel information
async getChannel(channelId) {
try {
const response = await axios.get(
`${this.serverUrl}/api/v4/channels/${channelId}`,
{ headers: this.headers }
);
return response.data;
} catch (error) {
console.error('Channel info error:', error.response.data);
throw error;
}
}
// WebSocket connection for real-time event monitoring
connectWebSocket() {
const WebSocket = require('ws');
const ws = new WebSocket(`${this.serverUrl.replace('http', 'ws')}/api/v4/websocket`, {
headers: { 'Authorization': `Bearer ${this.botToken}` }
});
ws.on('open', () => {
console.log('WebSocket connection established');
// Send authentication message
ws.send(JSON.stringify({
seq: 1,
action: 'authentication_challenge',
data: { token: this.botToken }
}));
});
ws.on('message', (data) => {
const event = JSON.parse(data);
this.handleWebSocketEvent(event);
});
return ws;
}
handleWebSocketEvent(event) {
switch (event.event) {
case 'posted':
this.handleNewPost(event.data.post);
break;
case 'user_added':
this.handleUserAdded(event.data);
break;
case 'channel_created':
this.handleChannelCreated(event.data.channel);
break;
default:
console.log('Unhandled event:', event.event);
}
}
async handleNewPost(postData) {
const post = JSON.parse(postData);
// Ignore bot's own posts
if (post.user_id === this.botUserId) return;
// Mention detection
if (post.message.includes(`@${this.botUsername}`)) {
await this.processCommand(post);
}
// Keyword monitoring
if (post.message.toLowerCase().includes('help')) {
await this.sendHelpMessage(post.channel_id);
}
}
async processCommand(post) {
const message = post.message.replace(`@${this.botUsername}`, '').trim();
const command = message.split(' ')[0].toLowerCase();
switch (command) {
case 'weather':
await this.handleWeatherCommand(post);
break;
case 'schedule':
await this.handleScheduleCommand(post);
break;
case 'report':
await this.handleReportCommand(post);
break;
default:
await this.sendMessage(post.channel_id, 'Unknown command. Type `help` to show help.');
}
}
}
// Usage example
const bot = new MattermostBot('https://your-mattermost.com', 'your-bot-token');
// Start WebSocket connection
bot.connectWebSocket();
// Regular status reporting
setInterval(async () => {
const statusMessage = {
message: '📊 System Status Report',
props: {
attachments: [{
color: '#28a745',
fields: [
{ title: 'CPU Usage', value: '45%', short: true },
{ title: 'Memory Usage', value: '67%', short: true },
{ title: 'Active Users', value: '234', short: true }
]
}]
}
};
await bot.sendMessage('status-channel-id', statusMessage.message, statusMessage.props);
}, 3600000); // Every hour
Management Operations with mmctl
# Authenticate to Mattermost server
mmctl auth login https://your-mattermost.com --name production --username admin
# Plugin management
mmctl plugin add hovercardexample.tar.gz
mmctl plugin enable hovercardexample
mmctl plugin list
mmctl plugin disable hovercardexample
# Bot creation and management
mmctl bot create mybot --description "Automation Bot" --display-name "Automation Bot"
mmctl bot list
mmctl bot enable mybot
# Webhook creation
mmctl webhook create-incoming \
--channel general \
--user admin \
--display-name "Deploy Notifications" \
--description "Deployment notification webhook"
# Team/Channel management
mmctl team create --name devops --display_name "DevOps Team"
mmctl channel create devops --name alerts --display_name "Alert Notifications"
# User management
mmctl user create --email [email protected] --username newuser --password password123
mmctl user activate newuser
mmctl team users add devops newuser
Environment Variables Configuration
# .env file
# Mattermost server settings
MATTERMOST_SERVER_URL=https://your-mattermost.com
MATTERMOST_BOT_TOKEN=your-bot-access-token
MATTERMOST_BOT_USERNAME=automation-bot
# Database settings (self-hosted)
MM_SQLSETTINGS_DRIVERNAME=postgres
MM_SQLSETTINGS_DATASOURCE=postgres://user:password@localhost/mattermost?sslmode=require
# File storage settings
MM_FILESETTINGS_DRIVERNAME=local
MM_FILESETTINGS_DIRECTORY=/opt/mattermost/data/
# Plugin settings
MM_PLUGINSETTINGS_ENABLE=true
MM_PLUGINSETTINGS_ENABLEUPLOADS=true
MM_PLUGINSETTINGS_AUTOMATICPREPACKAGEDPLUGINS=true
# Security settings
MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS=localhost
MM_SERVICESETTINGS_ENABLELOCALMODE=false
# External integrations
GITHUB_TOKEN=your-github-token
JIRA_API_KEY=your-jira-api-key
SLACK_WEBHOOK_URL=your-slack-webhook-url
Docker Compose Configuration Example
# docker-compose.yml
version: '3.8'
services:
mattermost:
image: mattermost/mattermost-enterprise-edition:latest
container_name: mattermost
ports:
- "8065:8065"
environment:
- MM_SQLSETTINGS_DRIVERNAME=postgres
- MM_SQLSETTINGS_DATASOURCE=postgres://mattermost:password@postgres:5432/mattermost?sslmode=disable
- MM_FILESETTINGS_DRIVERNAME=local
- MM_FILESETTINGS_DIRECTORY=/mattermost/data/
- MM_PLUGINSETTINGS_ENABLE=true
- MM_SERVICESETTINGS_SITEURL=https://your-mattermost.com
volumes:
- mattermost_data:/mattermost/data
- mattermost_logs:/mattermost/logs
- mattermost_config:/mattermost/config
- mattermost_plugins:/mattermost/plugins
depends_on:
- postgres
networks:
- mattermost-network
postgres:
image: postgres:13
container_name: mattermost_postgres
environment:
- POSTGRES_USER=mattermost
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mattermost
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- mattermost-network
nginx:
image: nginx:alpine
container_name: mattermost_nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- mattermost
networks:
- mattermost-network
volumes:
mattermost_data:
mattermost_logs:
mattermost_config:
mattermost_plugins:
postgres_data:
networks:
mattermost-network:
driver: bridge
Plugin Manifest Example
{
"id": "com.company.automation-plugin",
"name": "Automation Plugin",
"description": "DevOps automation plugin",
"version": "1.0.0",
"min_server_version": "9.0.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
},
"executable": ""
},
"webapp": {
"bundle_path": "webapp/dist/main.js"
},
"settings_schema": {
"header": "Plugin Settings",
"footer": "See [documentation](https://docs.company.com/automation-plugin) for details.",
"settings": [
{
"key": "EnableNotifications",
"display_name": "Enable Notifications",
"type": "bool",
"help_text": "Enable notifications for automation events.",
"default": true
},
{
"key": "APIEndpoint",
"display_name": "API Endpoint",
"type": "text",
"help_text": "Enter the external API endpoint URL.",
"placeholder": "https://api.company.com"
}
]
}
}