Getting Started with Teams Bots: Where Bot Framework Ends and Graph API Begins

5 min read

In Graph in Practice #6 we covered three routes for sending notifications into Teams and deferred full-blown bots to their own post. This post fills that gap. The conclusion up front: to converse in Teams under a “bot” identity, the Graph API alone is not enough — you need the Bot Framework (an Azure Bot). This post covers why, how a bot actually runs, and where Graph complements it.

Why Graph alone does not make a bot #

Graph has APIs that send chat messages, so “why not build the bot on those” is a natural thought. Two walls stand in the way.

  1. Identity: messages sent through Graph go out under the token owner’s name — a user or an automation account. Speaking as a bot, with a bot’s name and avatar, distinct from any person, goes through the Bot Framework.
  2. Application-permission limits: Graph calls that post regular messages to channels and chats are effectively closed to application permissions (the migration-only permission aside — the same constraint covered in #6). An unattended service conversing under its own name is simply outside Graph’s design.

So the roles split: conversation (receiving, replying, cards, buttons) is Bot Framework; organizational data (user lookups, files, calendars) is Graph, called from the bot’s backend. The two technologies are not rivals — they divide the labor inside one app.

How a bot runs: a web service receiving webhooks #

A bot is not mysterious. It is a web service with one HTTPS endpoint.

  1. Register an Azure Bot resource: the bot’s identity (app ID) and its messaging endpoint URL.
  2. Receive messages: when a user talks to the bot, the Bot Framework connector service POSTs that activity as JSON to your endpoint.
  3. Respond: your service replies with response activities via the SDK (C#, JavaScript, and Python supported).
  4. Teams app manifest: package the bot as a Teams app, publish to the org catalog, and users can install it.

The SDKs are convenient, but the connector service’s REST API can be called directly, so you are not locked to those languages. For local development, exposing the endpoint through a dev tunnel is the standard loop.

Proactive messages: how a bot speaks first #

The core requirement of a notification bot is “the bot sends before the user ever says anything.” Here the Bot Framework has an important rule: sending a proactive message requires a conversation reference — the addressing information a bot obtains from activity with that user or channel. Normally the user must have interacted with the bot at least once (or the bot must have received its installation event) for the reference to exist.

That leaves “how do I message someone who never installed it?” — and this is where Graph completes the bot. Using Graph’s proactive installation API (userteamwork/installedApps, application permission), you install the bot app for the target user; the installation event (conversationUpdate) then arrives at your bot endpoint, you capture the conversation reference there, and send the first message. This is the standard pattern for company-wide announcement bots.

The operational caution is frequency. Bursts of proactive messages can trip channel policies, and Teams enforces send-rate limits, so bulk sends should drain through a queue at a gentle pace.

Reading: RSC permissions for channel messages without a mention #

By default, a bot added to a channel receives only messages that @mention it. Bots that must hear everything in a channel (moderation, auto-tagging, AI assistants) declare RSC (Resource-Specific Consent) permissions in the app manifest. Permissions like ChannelMessage.Read.Group are consented by the team owner at install time — and the name’s point is that the consent is scoped to that one team, not the whole tenant. Receiving every message is powerful, which is why it should come with internal disclosure about what gets stored.

Adaptive Cards: from notification to interaction #

If a bot only sends text, it is no better than a webhook. The bot’s real power is Adaptive Cards — card UIs declared in JSON that carry input fields and buttons, whose actions return to your bot endpoint when clicked. “An approval notification with Approve/Reject buttons” and “a request card with a fill-in form” are built from exactly this. The moment a notification needs a button is the moment webhooks and Graph sends stop being enough and a bot becomes necessary — that is the boundary line for tool choice.

So where do you start: the ladder of requirements #

Stacking Teams messaging needs from the bottom:

  1. One-way notifications to a channel: a Workflows incoming webhook is enough (Graph in Practice #6).
  2. Activity feed notifications: Graph’s activity feed notification API sends badge-style alerts.
  3. Notifications under a bot’s name, minimal interaction: a notification-only bot — proactive messages plus cards, no receive logic.
  4. Conversation, buttons, forms: a full Bot Framework bot, with Graph in the backend for organizational data.

Deploying a higher rung than the requirement needs is the most common way Teams bot projects get heavy. The first design question is which rung you actually need.

Summary #

  • Graph alone does not make a bot: bot identity and conversation belong to the Bot Framework, and the application-permission limits on regular message sending still apply.
  • A bot is an HTTPS endpoint registered on an Azure Bot resource — activities arrive as JSON, replies go back through the SDK.
  • Proactive messages need a conversation reference; for users who never installed the bot, Graph’s proactive installation API manufactures the installation event that yields one. Design bulk sends around rate limits.
  • Reading a whole channel without mentions takes RSC permissions declared in the manifest and consented per team.
  • The need for buttons and forms is the boundary where bots become necessary; below that, webhooks and activity feed notifications keep it light.
X