A self-hosted bridge that relays messages between Slack, Microsoft Teams, and Google Chat. Post in a bridged channel on one platform and the message appears on the others, with the original sender’s name and avatar preserved where the platform allows.
Use it when your organization uses more than one chat system and you want a single shared channel (e.g. “engineering-general”) that stays in sync across all of them—without moving everyone to one product or maintaining separate threads everywhere.
Each platform runs a bot that listens for new messages and forwards them to the other connected platforms:
- Slack – Bot posts with the sender’s display name and avatar (
chat:write.customize). Messages show an “APP” badge. - Teams – Bot sends an Adaptive Card with the sender’s avatar, name, and a “via Slack” / “via Google Chat” label.
- Google Chat – Bot sends a Card v2 with the sender’s avatar and name in the card header.
Channels are linked with /bridge commands. Create a bridge group from any channel, then have each channel join that group—no config file edits or restarts.
- Node.js 20+
- Permission to create apps/bots on at least two of: Slack, Microsoft Teams, Google Chat
- Google Chat: paid Google Workspace and (for full setup) org admin for app approval
cd chat-bridge
npm install
cp config/bridge.example.yaml config/bridge.yaml
cp .env.example .env
# Add credentials for at least two platforms (see "Adding a new chat app" below)
npm run devThen link channels with /bridge create <name> and /bridge join <name> on each platform.
Setup is done via CLI tools where possible; anything that must be done in a browser or portal is documented in the /docs guides.
General flow:
- Install and authenticate the platform’s CLI (Slack CLI, gcloud, or Azure CLI).
- Run the matching setup script from the repo root. It will create or select resources and print the values to add to
.env. - Do any remaining steps in the UI (e.g. enable Socket Mode for Slack, configure the Chat app in Google Cloud, set the bot messaging endpoint for Teams, upload the Teams app package).
- In
config/bridge.yaml, setplatforms.<platform>.enabled: truefor that platform. - Restart the bridge and invite the bot into the channels you want to bridge.
Detailed steps per platform:
| Platform | CLI / script | Manual steps (browser/portal) documented in |
|---|---|---|
| Slack | ./scripts/setup-slack.sh |
docs/slack.md |
| Google Chat | ./scripts/setup-gcloud.sh |
docs/gchat.md |
| Microsoft Teams | ./scripts/setup-azure.sh |
docs/teams.md |
- Prereq: Slack CLI installed and
slack logindone. - Run:
./scripts/setup-slack.sh(optionallySLACK_TEAM_ID=T01234 ./scripts/setup-slack.sh). - Script: Lists workspaces, lets you pick one, prints the create-app URL and the path to
config/slack-app-manifest.yamlfor “Create from manifest.” - You do in the browser: Enable Socket Mode, create app-level token, install app, copy
SLACK_APP_TOKEN,SLACK_BOT_TOKEN,SLACK_SIGNING_SECRETinto.env. Full list: docs/slack.md.
- Prereq: gcloud installed and
gcloud auth logindone. - Run:
./scripts/setup-gcloud.sh(orGCP_PROJECT_ID=my-project ./scripts/setup-gcloud.sh). - Script: Enables Chat, Pub/Sub, and Workspace Events APIs; creates service account, topic, subscription, and JSON key at
config/gchat-service-account.json; prints env vars. - You do in the console: Configure the Chat app (name, avatar, connection = Pub/Sub topic,
/bridgecommand, permissions). Uncheck "Build this Chat app as a Workspace add-on" so the Cloud Pub/Sub option appears under Connection settings. Full steps: docs/gchat.md.
- Prereq: Azure CLI installed and
az logindone. - Run:
./scripts/setup-azure.sh(or setTEAMS_APP_NAME/AZURE_SUBSCRIPTION_ID/TEAMS_APP_IDto skip prompts). - Script: Creates (or reuses) Azure AD app, client secret, resource group, Azure Bot, and Teams channel; prints env vars.
- You do: Set messaging endpoint (
az bot update ... --endpoint "https://YOUR_PUBLIC_URL/api/teams/messages"), optionally add GraphUser.Read.Allfor avatars, create and upload the Teams app package. Full list: docs/teams.md.
After the bot is in channels on at least two platforms:
- In any channel, run:
/bridge create engineering-general(or any name). - In each channel you want in that bridge, run:
/bridge join engineering-general.
Messages in any linked channel are relayed to the others. Use /bridge status and /bridge list to inspect; /bridge leave to unlink.
| Command | Description |
|---|---|
/bridge create <name> |
Create a new bridge group |
/bridge join <group-id> |
Link this channel to a bridge group |
/bridge leave |
Unlink this channel |
/bridge status |
Show this channel’s bridge group |
/bridge list |
List all bridge groups |
-
Local / dev:
npm run devand expose with ngrok (ngrok http 3000). Set Teams bot messaging endpoint to the ngrok URL. -
Server: Build with
npm run build, run withnode dist/index.js(or a process manager like pm2). Setserver.publicUrlin config and use SSL (e.g. Let’s Encrypt). -
Docker: Build and run with a
Dockerfilethat copiesdist/,config/, and migrations; mountdata/andconfig/for persistence:FROM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY dist/ ./dist/ COPY config/ ./config/ COPY src/storage/migrations/ ./src/storage/migrations/ CMD ["node", "dist/index.js"]
npm run build docker build -t chat-bridge . docker run -d --name chat-bridge -p 3000:3000 \ -v $(pwd)/data:/app/data -v $(pwd)/config:/app/config \ --env-file .env chat-bridge
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Slack │ │ Teams │ │ GChat │
│ Adapter │ │ Adapter │ │ Adapter │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└────────┬───────┴────────┬───────┘
▼ ▼
┌──────────────────────────┐
│ Relay Engine │
└────────────┬─────────────┘
▼
┌──────────────────────────┐
│ Queue Manager (p-queue) │
│ Per-channel rate limits │
└────────────┬─────────────┘
▼
┌──────────────────────────┐
│ SQLite (better-sqlite3) │
│ Message/thread mappings │
│ User profile cache │
│ Bridge group config │
└──────────────────────────┘
- Storage: SQLite; single file, no extra services.
- Rate limits: Per-channel queues (e.g. 1 msg/sec for Slack and Google Chat).
- Sender identity: Slack uses
chat:write.customize; Teams and Google Chat use cards with avatar and name because bots can’t impersonate users. - Threads: Parent messages and replies are mapped so threads stay aligned across platforms.
- Bot doesn’t receive messages: Slack – bot invited to channel and
message.channels/message.groupssubscribed. Teams – messaging endpoint correct and reachable. Google Chat – Pub/Sub subscription and Workspace Events subscription in place (see docs/gchat.md). - Messages not relaying: Confirm channels are linked (
/bridge status), check logs (logging.level: "debug"), and ensure the bridge group has channels on at least two platforms. - Rate limits: Queues back off on 429; persistent errors may mean the channel is too active for the platform limit.
- Thread replies: Only messages that went through the bridge have thread mapping; older parents won’t link.
npm run dev # Hot reload (tsx watch)
npm run build # Compile TypeScript
npm run start # Run compiled build
npm test # Tests
npm run lint # LintMIT