100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Microsoft 365

Bots with the Bot Framework SDK

Teams bots are conversational agents built on the Bot Framework SDK, registered with Azure Bot Service, and driven by activity handlers that react to messages and events.

Building Teams AppsIntermediate10 min readJul 10, 2026
Analogies

How a Teams Bot Is Wired Together

A Teams bot isn't hosted by Teams itself; it's your own web service (Node.js, C#, or Python) that Azure Bot Service forwards conversations to via a messaging endpoint HTTPS URL. When a user sends a message, Teams posts an Activity object to that endpoint, your CloudAdapter authenticates the request, wraps it in a TurnContext, and routes it into an ActivityHandler subclass where you override methods like onMessageActivity to react. The bot's identity is a Microsoft App ID and secret registered in Azure, and that same App ID is what the manifest's bots array references as botId, tying the Teams app registration to the Azure bot registration.

🏏

Cricket analogy: The messaging endpoint receiving forwarded activities is like a stadium's PA announcer relaying the umpire's decision to the crowd; the umpire (Teams) makes the call, but a separate system (your bot service) broadcasts and acts on it.

Handling Conversations and Turns

Each incoming message is one turn, and the ActivityHandler dispatches based on activity.type: onMessageActivity for text, onMembersAddedActivity for when the bot is added to a team, and onTeamsMembersAddedActivity/onTeamsChannelCreatedEvent for Teams-specific events not present in the base Bot Framework. Within a turn, turnContext.sendActivity() replies synchronously in the same conversation, while conversation state (multi-turn context like "which step of a form is the user on") is persisted between turns using a ConversationState object backed by durable storage such as Azure Blob or Cosmos DB, since your bot process may not be the same instance handling the next message.

🏏

Cricket analogy: onMembersAddedActivity firing when the bot joins a team is like a new signing's unveiling ceremony at a franchise, a distinct one-time event separate from that player's actual performance in matches.

Proactive Messaging and Notifications

Not every bot message is a reply to user input. To send a notification the user didn't just trigger, such as an approval alert or a scheduled reminder, the bot needs a saved ConversationReference captured from a prior turn (typically in onMembersAddedActivity or the first message), then calls adapter.continueConversationAsync (or CloudAdapter.processProactive in newer SDKs) with that reference to open a new turn and send a message outside any active conversation. This requires the bot to have already been installed for that user or team, since Teams will not deliver a proactive message to someone who has never interacted with the app.

🏏

Cricket analogy: Saving a ConversationReference is like a broadcaster keeping a commentator's contact on file so they can be called back for a rain-delay update, without needing the original live feed still running.

typescript
import { TeamsActivityHandler, TurnContext, CardFactory } from "botbuilder";

export class HelpdeskBot extends TeamsActivityHandler {
  constructor() {
    super();

    this.onMessage(async (context: TurnContext, next) => {
      const text = context.activity.text?.trim().toLowerCase();

      if (text === "new ticket") {
        await context.sendActivity({
          attachments: [CardFactory.adaptiveCard(newTicketCard)]
        });
      } else {
        await context.sendActivity(`I didn't understand "${text}". Try "new ticket".`);
      }
      await next();
    });

    this.onMembersAdded(async (context, next) => {
      for (const member of context.activity.membersAdded ?? []) {
        if (member.id !== context.activity.recipient.id) {
          await context.sendActivity("Welcome! Type 'new ticket' to file a request.");
        }
      }
      await next();
    });
  }
}

Test bots locally with the Bot Framework Emulator or (for Teams-specific features like adaptive card actions and SSO) sideload the app into a real Teams client via ngrok or Dev Tunnels, since some Teams channel-specific payloads aren't fully reproducible in the generic emulator.

Proactive messaging fails silently with a 403/404 if the ConversationReference is stale (e.g., the user removed the app) or if the bot was never actually installed for that conversation. Always wrap proactive sends in error handling and consider re-installation prompts rather than assuming delivery succeeded.

  • A Teams bot is your own hosted service; Azure Bot Service forwards activities to its messaging endpoint over HTTPS.
  • ActivityHandler (or TeamsActivityHandler) methods like onMessage and onMembersAdded are the primary extension points.
  • The botId in manifest.json must match the Microsoft App ID from the Azure Bot Service registration.
  • ConversationState persists multi-turn context and must use durable storage since bot instances aren't guaranteed sticky.
  • Proactive messaging requires a saved ConversationReference and prior app installation for that user or conversation.
  • TeamsActivityHandler extends the base ActivityHandler with Teams-specific events like onTeamsMembersAddedActivity.
  • Local testing with Bot Framework Emulator won't catch every Teams-specific behavior; sideload for full verification.

Practice what you learned

Was this page helpful?

Topics covered

#Microsoft365#MicrosoftTeamsDevelopmentStudyNotes#MicrosoftTechnologies#BotsWithTheBotFrameworkSDK#Bots#Bot#Framework#SDK#StudyNotes#SkillVeris