Tutorials

Creating a Microsoft Teams Bot: No-Code Automation with n8n and Make.com

Step-by-step guide to creating a Teams Bot without code.

16 min read

Microsoft Teams has become indispensable in the workplace. But did you know you can create your own bots without programming? In this guide, we show you how to build Teams bots for notifications, interactions, and automation.

Why Build a Teams Bot?

Typical Use Cases:
Use CaseDescription
NotificationsServer alerts, orders, tickets
Interactive ActionsApprovals, surveys, feedback
Information RetrievalQuery status, search data
Process TriggersStart workflows from Teams
Benefits:
  • Directly in work context
  • No additional app needed
  • High user acceptance

Teams Messaging Options

1. Incoming Webhooks (Simplest Option)

For notifications only - no interaction.

Setup:
  • Teams Channel -> ... -> Connectors
  • Configure "Incoming Webhook"
  • Give it a name, copy URL
  • Send message (n8n):
    // Node: HTTP Request
    

    {

    "method": "POST",

    "url": "https://outlook.office.com/webhook/abc123...",

    "headers": { "Content-Type": "application/json" },

    "body": {

    "@type": "MessageCard",

    "@context": "http://schema.org/extensions",

    "themeColor": "0076D7",

    "summary": "New Order",

    "sections": [{

    "activityTitle": "Order #12345",

    "facts": [

    { "name": "Customer", "value": "John Smith" },

    { "name": "Amount", "value": "$149.00" }

    ],

    "markdown": true

    }]

    }

    }

    2. Outgoing Webhooks

    Teams sends messages to your endpoint.

    Setup:
  • Team Settings -> Apps -> Create Outgoing Webhook
  • Specify bot name and callback URL
  • Save HMAC token for verification
  • Receive messages (n8n):
    // Node: Webhook
    

    // Teams sends on @Mention

    {

    "type": "message",

    "text": "<at>BotName</at> What is the server status?",

    "from": {

    "name": "John Smith"

    },

    "channelId": "19:abc123..."

    }

    3. Bot Framework (Full-Featured)

    For complex interactions - requires Azure Bot Service.

    Workflow 1: Server Monitoring Alerts

    The Workflow

    Uptime Monitor (Server Down)
    

    |

    Teams Webhook:

    "Server is unreachable!"

    |

    With action buttons:

    [Acknowledge] [Escalate]

    Adaptive Cards for Rich Messages

    // Node: HTTP Request
    

    {

    "method": "POST",

    "url": "{{ $env.TEAMS_WEBHOOK_URL }}",

    "body": {

    "type": "message",

    "attachments": [

    {

    "contentType": "application/vnd.microsoft.card.adaptive",

    "content": {

    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",

    "type": "AdaptiveCard",

    "version": "1.4",

    "body": [

    {

    "type": "TextBlock",

    "size": "Large",

    "weight": "Bolder",

    "text": "Server Alert",

    "color": "Attention"

    },

    {

    "type": "FactSet",

    "facts": [

    { "title": "Server", "value": "{{ $json.server }}" },

    { "title": "Status", "value": "{{ $json.status }}" },

    { "title": "Time", "value": "{{ $now }}" }

    ]

    }

    ],

    "actions": [

    {

    "type": "Action.OpenUrl",

    "title": "Open Dashboard",

    "url": "https://monitoring.your-domain.com"

    }

    ]

    }

    }

    ]

    }

    }

    Workflow 2: Approval Workflow

    The Workflow

    New Vacation Request
    

    |

    Teams to Manager:

    "John wants vacation from Jan 15-20"

    [Approve] [Reject]

    |

    Manager clicks

    |

    n8n Webhook receives response

    |

    Update HR system

    Notify employee

    Adaptive Card with Actions

    {
    

    "type": "AdaptiveCard",

    "version": "1.4",

    "body": [

    {

    "type": "TextBlock",

    "size": "Large",

    "weight": "Bolder",

    "text": "Vacation Request"

    },

    {

    "type": "FactSet",

    "facts": [

    { "title": "Employee", "value": "{{ $json.employeeName }}" },

    { "title": "From", "value": "{{ $json.startDate }}" },

    { "title": "To", "value": "{{ $json.endDate }}" },

    { "title": "Days", "value": "{{ $json.days }}" }

    ]

    }

    ],

    "actions": [

    {

    "type": "Action.Http",

    "title": "Approve",

    "method": "POST",

    "url": "https://n8n.your-domain.com/webhook/vacation-approve",

    "body": "{ \"requestId\": \"{{ $json.requestId }}\", \"action\": \"approve\" }"

    },

    {

    "type": "Action.Http",

    "title": "Reject",

    "method": "POST",

    "url": "https://n8n.your-domain.com/webhook/vacation-approve",

    "body": "{ \"requestId\": \"{{ $json.requestId }}\", \"action\": \"reject\" }"

    }

    ]

    }

    Webhook for Response

    // Node: Webhook - Receives click
    

    const { requestId, action } = $json;

    if (action === 'approve') {

    await updateHRSystem(requestId, 'approved');

    await notifyEmployee(requestId, 'Your vacation has been approved!');

    } else {

    await updateHRSystem(requestId, 'rejected');

    await notifyEmployee(requestId, 'Your vacation request was unfortunately rejected.');

    }

    // Update card

    return {

    statusCode: 200,

    type: "application/json",

    body: {

    // Updated card

    }

    };

    Workflow 3: Daily Summary

    The Workflow

    Schedule (daily 9:00 AM)
    

    |

    Collect data:

    - Open tickets

    - Today's meetings

    - Upcoming deadlines

    |

    Teams: Send summary

    Implementation

    // Collect data
    

    const tickets = await getOpenTickets();

    const meetings = await getTodaysMeetings();

    const deadlines = await getUpcomingDeadlines();

    // Create Adaptive Card

    const card = {

    "type": "AdaptiveCard",

    "version": "1.4",

    "body": [

    {

    "type": "TextBlock",

    "size": "Large",

    "weight": "Bolder",

    "text": "Good morning! Here is your daily briefing"

    },

    {

    "type": "TextBlock",

    "text": <strong>${tickets.length} open tickets</strong>,

    "wrap": true

    },

    {

    "type": "TextBlock",

    "text": <strong>${meetings.length} meetings today</strong>,

    "wrap": true

    },

    {

    "type": "TextBlock",

    "text": <strong>${deadlines.length} upcoming deadlines</strong>,

    "wrap": true

    }

    ],

    "actions": [

    {

    "type": "Action.OpenUrl",

    "title": "Open Dashboard",

    "url": "https://dashboard.your-domain.com"

    }

    ]

    };

    Workflow 4: Interactive FAQ Bot

    With Power Virtual Agents (Low-Code)

  • Open Power Virtual Agents
  • Create new bot
  • Define topics (questions)
  • Configure answers
  • Publish to Teams
  • With n8n (Custom)

    Teams Outgoing Webhook
    

    (User asks: "How do I request vacation?")

    |

    Analyze text (keywords)

    |

    Switch:

    |-- "vacation" -> Vacation instructions

    |-- "password" -> Password reset info

    |-- "vpn" -> VPN instructions

    +-- else -> "I didn't understand that"

    |

    Send response to Teams

    Implementation

    // Node: Code - FAQ Matching
    

    const message = $json.text.toLowerCase();

    const faqs = {

    'vacation': 'Request vacation through the HR portal at hr.company.com/vacation',

    'password': 'Reset password: IT Portal -> Self-Service -> Password',

    'vpn': 'VPN access: Download the client from vpn.company.com',

    'sick': 'Sick leave: Please email hr@company.com with your doctor\'s note'

    };

    let response = 'Sorry, I don\'t have information on that. Please contact IT Support.';

    for (const [keyword, answer] of Object.entries(faqs)) {

    if (message.includes(keyword)) {

    response = answer;

    break;

    }

    }

    return { response };

    Make.com: Teams Modules

    Available Modules

  • Microsoft Teams -> Send a Message
  • Microsoft Teams -> Create a Channel
  • Microsoft Teams -> List Channels
  • Microsoft Teams -> Watch Messages (Trigger)
  • Example Scenario

    Microsoft Teams (Watch Messages)
    

    |

    Filter: Contains "help"

    |

    OpenAI: Generate response

    |

    Microsoft Teams: Reply

    Graph API for Extended Functions

    Send Direct Message

    // Node: HTTP Request
    

    {

    "method": "POST",

    "url": "https://graph.microsoft.com/v1.0/users/{{ $json.userId }}/chats/{{ $json.chatId }}/messages",

    "headers": {

    "Authorization": "Bearer {{ $json.accessToken }}",

    "Content-Type": "application/json"

    },

    "body": {

    "body": {

    "content": "Hello! Here is an important message."

    }

    }

    }

    Create Chat

    // Create 1:1 chat with user
    

    {

    "method": "POST",

    "url": "https://graph.microsoft.com/v1.0/chats",

    "body": {

    "chatType": "oneOnOne",

    "members": [

    {

    "@odata.type": "#microsoft.graph.aadUserConversationMember",

    "roles": ["owner"],

    "user@odata.bind": "https://graph.microsoft.com/v1.0/users/user1@company.com"

    },

    {

    "@odata.type": "#microsoft.graph.aadUserConversationMember",

    "roles": ["owner"],

    "user@odata.bind": "https://graph.microsoft.com/v1.0/users/user2@company.com"

    }

    ]

    }

    }

    Message Formats

    Simple Message

    {
    

    "text": "Hello, this is a simple message!"

    }

    With Markdown

    {
    

    "text": "<strong>Important:</strong> This is _formatted_ text with a link"

    }

    MessageCard (Legacy)

    {
    

    "@type": "MessageCard",

    "@context": "http://schema.org/extensions",

    "summary": "Summary",

    "themeColor": "0076D7",

    "title": "Message Title",

    "sections": [

    {

    "facts": [

    { "name": "Fact 1", "value": "Value 1" }

    ]

    }

    ],

    "potentialAction": [

    {

    "@type": "OpenUri",

    "name": "Learn More",

    "targets": [

    { "os": "default", "uri": "https://example.com" }

    ]

    }

    ]

    }

    Adaptive Card (Modern)

    Adaptive Cards offer more flexibility:

    • Input fields
    • Dropdowns
    • Date pickers
    • Actions with HTTP callbacks

    Best Practices

    1. Don't Send Too Many Messages

    // Batching: Collect multiple events
    

    const events = await collectEvents(5 <em> 60 </em> 1000); // 5 min

    if (events.length > 0) {

    await sendDigest(events);

    }

    2. Clear Actionable Messages

    // Bad
    

    { "text": "Something happened." }

    // Good

    {

    "title": "Server webserver-01 is down",

    "text": "Unreachable since 10:30 AM",

    "actions": [

    { "title": "Dashboard", "url": "..." },

    { "title": "Logs", "url": "..." }

    ]

    }

    3. Error Handling

    try {
    

    await sendTeamsMessage(message);

    } catch (error) {

    if (error.status === 429) {

    // Rate limit

    await wait(60000);

    await sendTeamsMessage(message);

    } else {

    // Fallback: Send email

    await sendEmail(message);

    }

    }

    Costs

    SolutionCost
    Incoming WebhooksFree
    Power Virtual AgentsFrom $200/month
    Azure Bot ServicePay-per-use
    n8n/Make.comFrom $9/month

    Conclusion

    Microsoft Teams bots are a powerful tool:

    • Notifications directly in work context
    • Interactive workflows (approvals, etc.)
    • FAQ bots for self-service

    Next Steps

  • Set up Incoming Webhook (quickest start)
  • Send first notification
  • Use Adaptive Cards for richer content
  • Add Actions for interactivity
  • We can help you with Teams automation, from setup to a production-ready bot.

    Questions About Automation?

    Our experts will help you make the right decisions for your business.