Package Information
Available Nodes
Documentation generating…
Documentation
n8n Teams Bot Node
Custom n8n node for sending messages to Microsoft Teams chats and channels via the Teams Bot API.
Features
- Chat Messages: Send direct messages to users by email address or by a known Teams conversation ID
- Channel Messages: Send messages to Teams channels by team name and channel name, or by a known channel ID
- @Mentions: Notify people by writing
@[their.email@example.com]in a text message - Adaptive Cards: Send rich, interactive Adaptive Cards with buttons, images, and formatted content
- Auto-population: Automatically loads available users, teams, and channels from your bot's database
- API Key Authentication: Secure authentication using API keys
Installation
Community Nodes (Recommended)
For self-hosted n8n instances:
- Go to Settings → Community nodes.
- Click Install a community node.
- Enter
n8n-nodes-teams-botand confirm you understand the risks of installing unverified code. - Click Install.
The Teams Bot node is then available in the nodes panel.
Manual Installation
In your n8n installation directory (or ~/.n8n/nodes for a self-hosted instance):
npm install n8n-nodes-teams-bot
Restart n8n afterwards.
Local Development
Important: If you're using WSL, run these commands from WSL bash, not Windows PowerShell/CMD.
npm install
npm run build
npm link
If npm run build fails due to Windows/WSL path issues, use:
bash build.sh
Then in your n8n installation directory:
npm link n8n-nodes-teams-bot
Note: See BUILD_INSTRUCTIONS.md for troubleshooting Windows/WSL build issues.
Railway Deployment
For Railway-hosted n8n instances, you have several options:
Option 1: Install from Git Repository (Recommended for Testing)
Push your node to a Git repository (GitHub, GitLab, etc.)
git init git add . git commit -m "Initial commit" git remote add origin https://github.com/john-vanmuyen/n8n-teams-node.git git push -u origin mainIn your Railway n8n service, modify the startup command:
Go to your Railway n8n service → Settings → Deploy
Option A: Modify the Start Command
Change the start command from:
n8n startTo:
npm install git+https://github.com/john-vanmuyen/n8n-teams-node.git && n8n startOption B: Use Railway's Build Command
In Railway service settings, set the Build Command to:
npm install && npm install git+https://github.com/john-vanmuyen/n8n-teams-node.gitAnd keep the Start Command as:
n8n startImportant: Make sure your node is built before installing. If installing from Git, you may need to:
- Build it locally and commit the
dist/folder, OR - Add a build step in Railway
- Build it locally and commit the
Option 2: Publish to npm (Recommended for Production)
Build and publish to npm:
npm run build npm publishFor private npm packages, use:
npm publish --access restrictedInstall in Railway n8n:
Add to your Railway service's environment or startup:
npm install n8n-nodes-teams-bot
Option 3: Use Railway Build Command
If you have access to modify Railway's build process:
Add a build script to your Railway n8n service that:
- Clones/installs your node
- Builds it
- Copies it to the n8n nodes directory
Example Railway build command:
npm install && \ npm install git+https://github.com/john-vanmuyen/n8n-teams-node.git && \ cd node_modules/n8n-nodes-teams-bot && \ npm install && npm run build
Option 4: Custom Dockerfile (Most Control)
Create a custom Dockerfile for your Railway n8n service:
FROM n8nio/n8n:latest
# Install your custom node
RUN npm install -g git+https://github.com/john-vanmuyen/n8n-teams-node.git
# Or if published to npm:
# RUN npm install -g n8n-nodes-teams-bot
Then configure Railway to use this Dockerfile.
Verifying Installation
After deployment, verify the node is loaded:
- Check n8n logs for any node loading errors
- In n8n UI, the "Teams Bot" node should appear in the node list
- Try creating a workflow with the node to confirm it works
Configuration
Credentials
- API Key: Your Teams Bot API key (set in your bot's
API_KEYenvironment variable) - Bot API URL: The base URL of your Teams Bot API (e.g.,
https://your-app.railway.app)
Node Operations
The node exposes a single Message → Send operation. Message Type selects
whether it goes to a chat or a channel.
Send Message to Chat
- Recipient Type: How to identify the recipient
By Email— look the user up by email addressBy Conversation ID— send straight to a known Teams conversation ID
- User Email (Recipient Type = By Email): Select from dropdown (auto-populated
via/api/list-users) or supply an expression - Conversation ID (Recipient Type = By Conversation ID): The user's 1-on-1
conversation ID with the bot, e.g.19:abc123@thread.v2. Returned in theconversation_idfield of/api/list-users. - Content Type: Choose between "Text" (plain text) or "Adaptive Card" (rich JSON card)
- Message: The message content to send (when Content Type is "Text")
- Adaptive Card JSON: The Adaptive Card JSON to send (when Content Type is "Adaptive Card")
Send Message to Channel
- Channel Target: How to identify the channel
By Team & Channel Name— look the channel up by team and channel nameBy Channel ID— send straight to a known Teams channel ID
- Team Name (Channel Target = By Team & Channel Name): Select from dropdown
(auto-populated via/api/list-channels) - Channel Name (Channel Target = By Team & Channel Name): Select from dropdown,
filtered by the selected team - Channel ID (Channel Target = By Channel ID): The Teams channel ID, e.g.
19:xxx@thread.tacv2 - Content Type: Choose between "Text" (plain text) or "Adaptive Card" (rich JSON card)
- Message: The message content to send (when Content Type is "Text")
- Adaptive Card JSON: The Adaptive Card JSON to send (when Content Type is "Adaptive Card")
Targeting by ID skips the bot's name lookup, so it is both faster and immune to
teams or channels being renamed.
Mentions
Write @[someone@example.com] anywhere in a Message to turn it into a real
Teams @mention that notifies that person. It works in both chat and channel
messages, and composes with expressions:
@[jane@corp.ca] ticket {{ $json.id }} was escalated — @[{{ $json.owner }}] fyi
The node extracts the addresses and sends them alongside the unmodified text.
Your bot resolves each one to a Teams user and rewrites the token, so the
recipient sees @Jane Doe, not the bracket syntax.
Requires bot support. A Teams mention has to be built into the outgoing Bot
Framework activity, which only the bot can do. If your bot does not handle thementions field, tokens arrive as literal @[...] text. See
API Endpoints Used for the contract.
Resolution order: the bot checks its stored users first, then falls back to
the team roster. So you can mention anyone on the team, not only people who have
already messaged the bot.
Unresolved addresses degrade rather than fail. An address the bot cannot
match is delivered as plain text with no notification, and is listed inunresolved_mentions on the node's output item — branch on it if a missed ping
matters to your workflow. The message itself always sends.
Some details worth knowing:
- Only text messages. Mentions inside Adaptive Cards use a different mechanism
and are not supported — sending both is rejected rather than silently ignored. - Only email addresses.
@[TODO],@[see notes], and an@[]left by an
expression that resolved to nothing are all left alone as ordinary text. - There is no escape syntax, so a literal
@[someone@example.com]cannot be
written as-is.
Adaptive Cards
Adaptive Cards let you send rich, interactive content to Microsoft Teams. You can create cards with:
- Formatted text and images
- Action buttons (open URLs, submit data)
- Forms and input fields
- Tables and data displays
Design your cards: Use the Adaptive Cards Designer to visually create cards, then copy the JSON into the node.
Example Adaptive Card:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "🚀 Deployment Notification",
"size": "Large",
"weight": "Bolder"
},
{
"type": "FactSet",
"facts": [
{"title": "Service", "value": "API Gateway"},
{"title": "Version", "value": "2.1.0"},
{"title": "Status", "value": "✅ Complete"}
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "View Logs",
"url": "https://example.com/logs"
}
]
}
Requirements
Your Teams Bot API must have the following endpoints:
POST /api/send-message- Send messages to usersPOST /api/send-to-channel- Send messages to channels- Both send endpoints must accept the optional
mentionsarray for @mention support GET /api/list-channels- List available channels (for auto-population)GET /api/list-users- List available users (for auto-population)
Note: The /api/list-users endpoint is required for auto-populating user emails. If it doesn't exist in your bot, you can manually enter email addresses, but it's recommended to add this endpoint to your bot API.
Adding the /api/list-users Endpoint
To enable auto-population of user emails, add this endpoint to your Teams Bot API (app.py):
# List all users with personal conversations
async def list_users(req: Request) -> Response:
"""
List all users who have personal conversations with the bot.
Returns users with email addresses for use in integrations.
"""
try:
from utils.postgres_storage import get_postgres_store
store = get_postgres_store()
async with store.pool.acquire() as conn:
# Get only personal conversations (not channels)
rows = await conn.fetch("""
SELECT user_id, user_name, user_email, conversation_id
FROM users
WHERE conversation_type = 'personal'
AND user_email IS NOT NULL
AND user_email !~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
ORDER BY user_email
""")
users = [dict(row) for row in rows]
return json_response({
"total": len(users),
"users": users
})
except Exception as e:
print(f"Error listing users: {str(e)}", file=sys.stderr)
return json_response(
{"error": f"Failed to list users: {str(e)}"},
status=HTTPStatus.INTERNAL_SERVER_ERROR
)
# Add to protected endpoints
PROTECTED_ENDPOINTS = [
"/api/send-message",
"/api/send-to-channel",
"/api/update-email",
"/api/list-users", # Add this
]
# Add route
APP.router.add_get("/api/list-users", list_users)
API Endpoints Used
Send Message to Chat (Plain Text)
POST /api/send-message
Headers:
X-API-Key: <your-api-key>
Body:
{
"user_email": "user@example.com",
"message": "Your message here"
}
Send Message with Mentions
The node adds a mentions array when it finds @[email] tokens in the text.
The message is sent unchanged — the bot does the rewriting.
POST /api/send-message
Headers:
X-API-Key: <your-api-key>
Body:
{
"user_email": "user@example.com",
"message": "@[jane@corp.ca] ticket #4471 was escalated",
"mentions": [
{"email": "jane@corp.ca"}
]
}
The same field works on /api/send-to-channel. The response reports what was
resolved:
{
"status": "success",
"mentions_resolved": 1,
"unresolved_mentions": []
}
When no tokens are found the node omits mentions entirely, and the request is
byte-identical to one from a node without mention support.
Send Message to Chat (Adaptive Card)
POST /api/send-message
Headers:
X-API-Key: <your-api-key>
Body:
{
"user_email": "user@example.com",
"adaptive_card": {
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{"type": "TextBlock", "text": "Hello!", "size": "Large"}
]
}
}
Send Message to Chat (By Conversation ID)
POST /api/send-message
Headers:
X-API-Key: <your-api-key>
Body:
{
"conversation_id": "19:abc123@thread.v2",
"message": "Your message here"
}
Send Message to Channel (Plain Text)
POST /api/send-to-channel
Headers:
X-API-Key: <your-api-key>
Body:
{
"team_name": "Engineering Team",
"channel_name": "General",
"message": "Your message here"
}
Send Message to Channel (Adaptive Card)
POST /api/send-to-channel
Headers:
X-API-Key: <your-api-key>
Body:
{
"team_name": "Engineering Team",
"channel_name": "General",
"adaptive_card": {
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{"type": "TextBlock", "text": "Channel Announcement!", "size": "Large"}
]
}
}
Send Message to Channel (By Channel ID)
POST /api/send-to-channel
Headers:
X-API-Key: <your-api-key>
Body:
{
"channel_id": "19:xxx@thread.tacv2",
"message": "Your message here"
}
Either targeting style accepts adaptive_card in place of message.
List Channels (for auto-population)
GET /api/list-channels
Headers:
X-API-Key: <your-api-key>
Development
# Install dependencies
npm install
# Build the node
npm run build
# Watch for changes during development
npm run dev
# Lint code
npm run lint
# Format code
npm run format
Node Structure
This node uses n8n's programmatic style — requests are issued from anexecute() method rather than declared with routing:
- Node properties are declared in the
descriptionobject - Resources and operations are defined as options
- Field visibility is controlled using
displayOptions - Auto-population methods are defined in the
methods.loadOptionsobject - Requests are built and sent in
execute(), which honourscontinueOnFail()
License
MIT