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. If you use Slack instead, check out our Slack bot setup guide.

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:

  1. Teams Channel -> ... -> Connectors
  2. Configure "Incoming Webhook"
  3. 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:

  1. Team Settings -> Apps -> Create Outgoing Webhook
  2. Specify bot name and callback URL
  3. 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": `**${tickets.length} open tickets**`,
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": `**${meetings.length} meetings today**`,
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": `**${deadlines.length} upcoming deadlines**`,
      "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)

  1. Open Power Virtual Agents
  2. Create new bot
  3. Define topics (questions)
  4. Configure answers
  5. 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

  1. Microsoft Teams -> Send a Message
  2. Microsoft Teams -> Create a Channel
  3. Microsoft Teams -> List Channels
  4. 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": "**Important:** This is _formatted_ text with a [link](https://example.com)"
}

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 * 60 * 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

  1. Set up Incoming Webhook (quickest start)
  2. Send first notification
  3. Use Adaptive Cards for richer content
  4. Add Actions for interactivity

We can help you with Teams automation, from setup to a production-ready bot. Contact us for a free consultation. For broader automation ideas, see our workflow automation examples and our guide to customer service automation.