A TypeScript-based bridge that forwards messages between channels on different Mattermost instances. Uses regular user accounts for authentication - no bot setup required!
This project was vibe coded with Claude Opus 4
The left mattermost (the one we want to monitor messages on) has a user posting a message:
The bridge will mirror the message to the right mattermost (the destination mattermost)
The console / app log
The bridge includes a built-in status channel that provides real-time monitoring of bridge activity and logs. When enabled with STATS_CHANNEL_UPDATES=logs
, it creates a dedicated #mattermost-bridge-status channel that continuously updates with:
- Event summaries - Message counts and bridge activity
- Live log feed - Last 30 log lines updated every 10 minutes (respects TIMEZONE setting)
- Single message updates - No channel spam, just one continuously updated status message
This gives you an always up-to-date view of your bridge health without needing to check Docker logs or the console.
The bridge listens for messages on a source channel and forwards them to a target channel on a different Mattermost instance, preserving:
- User avatars
- File attachments
- Message formatting
- Original context (timestamp, channel, server)
- Cross-server messaging - Bridge channels between any two Mattermost instances
- Profile picture sync - Downloads and re-uploads user avatars with intelligent caching
- File attachment support - Seamlessly forwards all file attachments
- MFA/2FA support - Works with multi-factor authentication enabled accounts
- Email domain filtering - Exclude messages from specific email domains
- Minimal attachments - Clean, baby blue message formatting with profile pictures
- Message catch-up - Automatically recover missed messages when bridge restarts (with Docker volume persistence)
# Clone and setup
git clone <repository-url>
cd mattermost-bridge
npm install
# Run quick setup
./quick-start.sh
The bridge uses environment variables for configuration. You can set these in a .env
file or as environment variables in your deployment environment.
# Required: Mattermost Instances
MATTERMOST_LEFT_NAME=SourceServer
MATTERMOST_LEFT_SERVER=https://mattermost.source.com
MATTERMOST_LEFT_USERNAME=[email protected]
MATTERMOST_LEFT_PASSWORD_B64=<base64-encoded-password>
MATTERMOST_LEFT_TEAM=team-name
MATTERMOST_RIGHT_NAME=TargetServer
MATTERMOST_RIGHT_SERVER=https://mattermost.target.com
MATTERMOST_RIGHT_USERNAME=[email protected]
MATTERMOST_RIGHT_PASSWORD_B64=<base64-encoded-password>
# Required: Channel IDs
SOURCE_CHANNEL_ID=abc123def456... # Single channel
# or
SOURCE_CHANNEL_ID=abc123,def456,ghi789 # Multiple channels (comma-separated)
TARGET_CHANNEL_ID=xyz789uvw012...
Variable | Description | Required | Default | Example |
---|---|---|---|---|
Left Mattermost (Source) | ||||
MATTERMOST_LEFT_NAME |
Display name for the source server | ✅ | - | SourceServer |
MATTERMOST_LEFT_SERVER |
URL of the source Mattermost server | ✅ | - | https://mattermost.source.com |
MATTERMOST_LEFT_USERNAME |
Username for authentication | ✅ | - | [email protected] |
MATTERMOST_LEFT_PASSWORD_B64 |
Base64-encoded password | ✅ | - | cGFzc3dvcmQxMjMh |
MATTERMOST_LEFT_TEAM |
Team name (for generating message links) | ✅ | - | team-name |
MATTERMOST_LEFT_MFA_SEED |
MFA/2FA seed (if MFA enabled) | ❌ | - | JBSWY3DPEHPK3PXP |
Right Mattermost (Target) | ||||
MATTERMOST_RIGHT_NAME |
Display name for the target server | ✅ | - | TargetServer |
MATTERMOST_RIGHT_SERVER |
URL of the target Mattermost server | ✅ | - | https://mattermost.target.com |
MATTERMOST_RIGHT_USERNAME |
Username for authentication | ✅ | - | [email protected] |
MATTERMOST_RIGHT_PASSWORD_B64 |
Base64-encoded password | ✅ | - | cGFzc3dvcmQxMjMh |
MATTERMOST_RIGHT_TEAM |
Team name (for message links) | ❌ | - | team-name |
MATTERMOST_RIGHT_MFA_SEED |
MFA/2FA seed (if MFA enabled) | ❌ | - | GEZDGNBVGY3TQOJQ |
Bridge Configuration | ||||
SOURCE_CHANNEL_ID |
ID of the channel(s) to monitor. Can be a single ID or comma-separated list | ✅ | - | abc123def456... or abc123,def456,ghi789 |
TARGET_CHANNEL_ID |
ID of the channel to post to | ✅ | - | xyz789uvw012... |
Logging & Display | ||||
LOG_LEVEL |
Logging verbosity level | ❌ | info |
debug , info , warn , error |
DEBUG_WEBSOCKET_EVENTS |
Enable detailed WebSocket event logging | ❌ | false |
true |
EVENT_SUMMARY_INTERVAL_MINUTES |
How often to log event summaries | ❌ | 10 |
5 |
STATS_CHANNEL_UPDATES |
What to post to #mattermost-bridge-status | ❌ | none |
summary , logs |
none = disable status channel, summary = post event summaries, logs = combined summaries + last 30 log lines (updates single message) |
||||
DISABLE_EMOJI |
Disable emojis in console output | ❌ | false |
true |
TIMEZONE |
Timezone for timestamp formatting | ❌ | UTC |
Europe/Brussels , CET |
Message Filtering | ||||
DONT_FORWARD_FOR |
Comma-separated email domains to exclude | ❌ | - | @excluded.com,@internal.org |
DRY_RUN |
Log messages without posting to target | ❌ | false |
true |
Monitoring | ||||
HEARTBEAT_URL |
URL for uptime monitoring | ❌ | - | https://heartbeat.uptimerobot.com/... |
HEARTBEAT_INTERVAL_MINUTES |
How often to send heartbeat pings | ❌ | 15 |
5 |
Message Catch-Up | ||||
ENABLE_CATCH_UP |
Enable catch-up mode to recover missed messages when bridge was offline | ❌ | false |
true |
CATCH_UP_PERSISTENCE_PATH |
Path to store message tracking state (Docker volume recommended) | ❌ | /data/tracking/message-state.json |
/custom/path/state.json |
MAX_MESSAGES_TO_RECOVER |
Maximum number of messages to recover per channel on startup | ❌ | 100 |
50 , 200 |
Appearance | ||||
FOOTER_ICON |
Custom icon URL for message footers | ❌ | - | https://example.com/icon.png |
LEFT_MESSAGE_EMOJI |
Emoji to add to original message after bridging | ❌ | - | envelope_with_arrow , white_check_mark |
node encode-password.js "your-password-here"
# Pull and run
docker pull clnio/mattermost-bridge:latest
docker run --env-file .env clnio/mattermost-bridge:latest
# Or use docker-compose
docker-compose up -d
Docker images support both AMD64 and ARM64 architectures.
To enable message catch-up functionality, mount a volume for persistence:
# Create a named volume
docker volume create mattermost-bridge-data
# Run with catch-up enabled
docker run --env-file .env \
-e ENABLE_CATCH_UP=true \
-v mattermost-bridge-data:/data \
clnio/mattermost-bridge:latest
Or in docker-compose.yml:
version: '3.8'
services:
mattermost-bridge:
image: clnio/mattermost-bridge:latest
env_file: .env
environment:
- ENABLE_CATCH_UP=true
volumes:
- mattermost-bridge-data:/data
restart: unless-stopped
volumes:
mattermost-bridge-data:
The bridge will automatically track forwarded messages and catch up on missed messages when restarted.
# Set up local test environment
./local-env-setup.sh
# Start development
npm run dev
# Run tests
npm test
- Left: http://localhost:8065/left (user:
left
/leftpass123!
) - Right: http://localhost:9065/right (user:
right
/rightpass123!
)
Authentication Failed
- Verify base64 encoded passwords
- Check MFA seed if using 2FA
- Ensure user has channel access
Channel Not Found
- Verify channel IDs are correct
- Use
mmctl channel search "channel-name" --team "team-name"
Debug Mode
LOG_LEVEL=debug
DEBUG_WEBSOCKET_EVENTS=true
npm start
- Start the bridgenpm run dev
- Development modenpm test
- Run tests./build.sh
- Build Docker image./push-live.sh
- Push to Docker Hub./local-env-setup.sh
- Set up local test environment
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
MIT License - see the LICENSE file for details.