-
Notifications
You must be signed in to change notification settings - Fork 307
Send notification to Teams
We appreciate your feedback, please report any issues to us here.
The Notification in Teams feature enables you to build applications that consume events and send these as notifications to an individual person, a chat, or a channel in Teams. Notifications can be sent as plain text or Adaptive Cards.
In this tutorial, you will learn:
- Send notification to Teams via Teams bot application
- Different approaches to send notifications to Teams
- Send notification to Teams via Teams incoming webhook
- From Teams Toolkit side bar click
Create a new Teams appor selectTeams: Create a new Teams appfrom the command palette.

- Select
Create a new Teams app.

- Select
Notification botfrom Scenario-based Teams app section.

- Select triggers. You can choose from
HTTP TriggerorTimer Trigger. The triggers are based onRestify Server(means the created app code is a restify web app) orAzure Functions(means the created app code is Azure Functions).

- Select programming language

- Enter an application name and then press enter.

-
Make sure you have installed ASP.NET workloads and "Microsoft Teams development tools".

-
Create a new project and select "Microsoft Teams App".

-
In next window enter your project name.
-
In next window select Notification Bot. Then select a trigger type in right panel. You can choose from HTTP Trigger or Timer Trigger. The triggers are based on WebApi Server (means the created app code is a web app) or Azure Functions (means the created app code is Azure Functions)..

-
If you prefer interactive mode, execute
teamsfx newcommand, then use the keyboard to go through the same flow as in Visual Studio Code. -
If you prefer non-interactive mode, enter all required parameters in one command.
teamsfx new --interactive false --capabilities "notification" --bot-host-type-trigger "http-restify" --programming-language "typescript" --folder "./" --app-name MyAppName
After you successfully created the project, you can quickly start local debugging via F5 in VSCode. Select Debug (Edge) or Debug (Chrome) debug option of your preferred browser. If you created a timer triggered notification, after running this template and you will get a notification as below:

The created app is a normal TeamsFx project that will contain following folders:
| Folder | Contents |
|---|---|
.fx |
Project level settings and configurations |
.vscode |
VSCode files for local debug |
bot |
The bot source code |
templates |
Templates for Teams app manifest and corresponding Azure resources |
If you select (Restify) trigger(s), the bot/ folder is restify web app with following content:
| File / Folder | Contents |
|---|---|
src/adaptiveCards/ |
Adaptive card templates |
src/internal/ |
Generated initialize code for notification functionality |
src/cardModels.*s |
Adaptive card data models |
src/index.*s |
The entrypoint to handle bot messages and send notifications |
.gitignore |
The git ignore file to exclude local files from bot project |
package.json |
The NPM package file for bot project |
If you select (Azure Functions) trigger(s), the bot/ folder is azure functions app with following content:
| File / Folder | Contents |
|---|---|
messageHandler/ |
The function to handle bot messages |
*Trigger/ |
The function to trigger notification |
src/adaptiveCards/ |
Adaptive card templates |
src/internal/ |
Generated initialize code for notification functionality |
src/cardModels.*s |
Adaptive card data models |
src/*Trigger.*s |
The entrypoint of each notification trigger |
.funcignore |
The azure functions ignore file to exclude local files |
.gitignore |
The git ignore file to exclude local files from bot project |
host.json |
The azure functions host file |
local.settings.json |
The azure functions local setting file |
package.json |
The NPM package file for bot project |
If you selected WebApi Http trigger, the project structure would like this:
| Folder | Contents |
|---|---|
Properties |
LaunchSetting file for local debug |
.fx |
Project level settings and configurations |
Controllers |
BotController and NotificationControllers to handle the message content |
Models |
Adaptive card data models |
Resources |
Adaptive card templates |
Templates |
Templates for Teams app manifest and corresponding Azure resources |
appsettings |
The Bot settings |
GettingStarted.txt |
Instructions on minimal steps to wonderful |
Program.cs |
Create the Teams Bot instance |
TeamsBot.cs |
An empty Bot handler |
If you selected Timer trigger, the project structure would be generally the same, only differences are listed in below table:
| Folder | Contents |
|---|---|
MessageHandler.cs |
Instead of Controller folder, MessageHandler is where you can define the message content |
NotifyTimerTrigger.cs |
Define the time interval to trigger the notification |
Startup.cs |
Instead of Program.cs, Startup.cs create the Teams Bot instanceΒ Β |
If you selected Azure Functions based HTTP trigger, the project structure would be generally the same, only differences are listed in below table:
| Folder | Contents |
|---|---|
MessageHandler.cs |
Start an asynchronized task to call http request |
NotifyHttpTrigger.cs |
Handle the request result and convert to message content |
Startup.cs |
Instead of Program.cs, Startup.cs create the Teams Bot instanceΒ Β |
To send notification, you need to create ConversationBot first. (Code already generated in project)
/** Javascript/Typescript: bot/src/internal/initialize.*s **/
const bot = new ConversationBot({
// The bot id and password to create BotFrameworkAdapter.
// See https://aka.ms/about-bot-adapter to learn more about adapters.
adapterConfig: {
appId: process.env.BOT_ID,
appPassword: process.env.BOT_PASSWORD,
},
// Enable notification
notification: {
enabled: true,
},
});/** .NET: Program.cs or Startup.cs **/
// Create the Conversation with notification feature enabled.
builder.Services.AddSingleton(sp =>
{
var options = new ConversationOptions()
{
// To use your own CloudAdapter
Adapter = sp.GetService<CloudAdapter>(),
Notification = new NotificationOptions
{
BotAppId = builder.Configuration["MicrosoftAppId"],
},
};
return new ConversationBot(options);
});You can initialize with your own adapter, or customize after initialization.
/** Typescript **/
// Create your own adapter
const adapter = new BotFrameworkAdapter(...);
// Customize your adapter, e.g., error handling
adapter.onTurnError = ...
const bot = new ConversationBot({
// use your own adapter
adapter: adapter;
...
});
// Or, customize later
bot.adapter.onTurnError = ...You can initialize with your own storage. This storage will be used to persist notification connections.
/** Typescript **/
// implement your own storage
class MyStorage implements NotificationTargetStorage {...}
const myStorage = new MyStorage(...);
// initialize ConversationBot with notification enabled and customized storage
const bot = new ConversationBot({
// The bot id and password to create BotFrameworkAdapter.
// See https://aka.ms/about-bot-adapter to learn more about adapters.
adapterConfig: {
appId: process.env.BOT_ID,
appPassword: process.env.BOT_PASSWORD,
},
// Enable notification
notification: {
enabled: true,
storage: myStorage,
},
});/** .NET **/
// implement your own storage
public class MyStorage : INotificationTargetStorage {...}
// initialize ConversationBot with notification enabled and customized storage
builder.Services.AddSingleton(sp =>
{
var options = new ConversationOptions()
{
Adapter = sp.GetService<CloudAdapter>(),
Notification = new NotificationOptions
{
BotAppId = builder.Configuration["MicrosoftAppId"],
// Use your own storage
Storage = new MyStorage(),
},
};
return new ConversationBot(options);
});This Sample provides a sample implementation that persists to Azure Blob Storage.
Note: It's recommended to use your own shared storage for production environment. If
storageis not provided, a default local file storage will be used, which stores notification connections into:
- .notification.localstore.json if running locally
- ${process.env.TEMP}/.notification.localstore.json if
process.env.RUNNING_ON_AZUREis set to "1"
A Teams bot can be installed into a team, or a group chat, or as personal app, depending on difference scopes. You can choose the installation target when adding the App. (See Distribute your app for more install options.)

To send notification in team/channel:
/** Typescript **/
// list all installation targets
for (const target of await bot.notification.installations()) {
// "Channel" means this bot is installed to a Team (default to notify General channel)
if (target.type === "Channel") {
// Directly notify the Team (to the default General channel)
await target.sendAdaptiveCard(...);
// List all members in the Team then notify each member
const members = await target.members();
for (const member of members) {
await member.sendAdaptiveCard(...);
}
// List all channels in the Team then notify each channel
const channels = await target.channels();
for (const channel of channels) {
await channel.sendAdaptiveCard(...);
}
}
}/** .NET **/
// list all installation targets
foreach (var target in await _conversation.Notification.GetInstallationsAsync()) {
// "Channel" means this bot is installed to a Team (default to notify General channel)
if (target.Type == NotificationTargetType.Channel)
{
// Directly notify the Team (to the default General channel)
await target.SendAdaptiveCard(...);
// List all members in the Team then notify each member
var members = await target.GetMembersAsync();
foreach (var member in members) {
await member.SendAdaptiveCard(...);
}
// List all channels in the Team then notify each channel
var channels = await target.GetChannelsAsync();
foreach (var channel in channels) {
await channel.SendAdaptiveCard(...);
}
}
}To send notification in group chat
/** Typescript **/
// list all installation targets
for (const target of await bot.notification.installations()) {
// "Group" means this bot is installed to a Group Chat
if (target.type === "Group") {
// Directly notify the Group Chat
await target.sendAdaptiveCard(...);
// List all members in the Group Chat then notify each member
const members = await target.members();
for (const member of members) {
await member.sendAdaptiveCard(...);
}
}
}/** .NET **/
// list all installation targets
foreach (var target in await _conversation.Notification.GetInstallationsAsync()) {
// "Group" means this bot is installed to a Group Chat
if (target.Type == NotificationTargetType.Group)
{
// Directly notify the Group Chat
await target.SendAdaptiveCard(...);
// List all members in the Group Chat then notify each member
var members = await target.GetMembersAsync();
foreach (var member in members) {
await member.SendAdaptiveCard(...);
}
}
}To send notification in personal chat
/** Typescript **/
// list all installation targets
for (const target of await bot.notification.installations()) {
// "Person" means this bot is installed as Personal app
if (target.type === "Person") {
// Directly notify the individual person
await target.sendAdaptiveCard(...);
}
}/** .NET **/
// list all installation targets
foreach (var target in await _conversation.Notification.GetInstallationsAsync()) {
// "Person" means this bot is installed as Personal app
if (target.Type == NotificationTargetType.Person)
{
// Directly notify the individual person
await target.SendAdaptiveCard(...);
}
}If you choose http trigger, the scaffolded notification API does not have authentication / authorization enabled. We suggest you add authentication / authorization for this API before using it for production purpose. Here're some common ways to add authentication / authorization for an API:
-
Use an API Key. If you chose Azure Functions to host your notification bot, it already provides function access keys, which may be helpful to you.
-
Use an access token issued by Azure Active Directory
There would be more authentication / authorization solutions for an API. You can choose the one that satisfies your requirement best.
Technically, Bot Framework SDK provides the functionality to proactively message in Teams. And TeamsFx SDK provides the functionality to manage bot's conversation references when bot event is triggered.
Current TeamsFx SDK recognize following bot events:
| Event | Behavior |
|---|---|
| The first time Bot is added(installed) to Person/Group/Team | Add the target conversation reference to storage |
| Bot is removed(uninstalled) from Person/Group/Team | Remove the target conversation reference from storage |
| Team that bot installed in is deleted | Remove the target conversation reference from storage |
| Team that bot installed in is restored | Add the target conversation reference to storage |
| Bot is messaged/mentioned | Add the target conversation reference to storage if not exist |
When notifying, TeamsFx SDK creates new conversation from the selected conversation reference and send messages. Or, for advanced usage, you can directly access the conversation reference to execute your own bot logic:
/** Typescript **/
// list all installation targets
for (const target of await bot.notification.installations()) {
// call Bot Framework's adapter.continueConversation()
await target.adapter.continueConversation(target.conversationReference, async (context) => {
// your own bot logic
await context...
});
}/** .NET **/
// list all installation targets
foreach (var target in await _conversation.Notification.GetInstallationsAsync()) {
// call Bot Framework's adapter.ContinueConversationAsync()
await target.Adapter.ContinueConversationAsync(
target.BotAppId,
target.ConversationReference,
async (context, ctx) =>
{
// your own bot logic
await context...
},
cancellationToken);
}If you want to invoke external APIs in your code but do not have the appropriate SDK, the "Teams: Connect to an API" command in Teams Toolkit VS Code extension or "teamsfx add api-connection" command in TeamsFx CLI would be helpful to bootstrap code to call target APIs. For more information, you can visit Connect existing API document.
It depends on your host type.
-
If you created Restify notification project, you can add HTTP trigger(s) by creating new routing
/** Javascript/Typescript **/ server.post("/api/new-trigger", ...);
Or add Timer trigger(s) via widely-used npm packages such as cron, node-schedule, etc.
Or add other trigger(s) via other packages.
-
If you created Azure Functions notification project, you can add any Azure Functions trigger(s) with your own
function.jsonfile and code file(s). Azure Functions supported triggers.
One possible cause is that the installation event was omitted or did not reached to the bot service. Teams only send such event at the first installation time, so if the bot app was already installed before your notification bot service is launched, you are probably in this case.
There are two options to fix this:
- Send a message to your Personal bot or mention your bot in GroupChat / Channel. (This is to re-reach the bot service with correct installation information)
- Uninstall the bot app from Teams then re-debug/re-launch again. (This is to re-send the installation event to bot service)
Technically, notification target connections are stored in the persistence storage. If you are using the default local file storage, all installations will be stored under bot/.notification.localstore.json. Or refer to Customize Storage to add your own storage.
One possible cause is that the bot id or password is changed (usually due to cleaning local state or re-provisioning). E.g., if the notification installation does not match the bot (id/password) you are running, you may get a "Failed to decrypt conversation id" error.
And a quick fix could be:
-
Clean your notification storage (by default for local case it's
.notification.localstore.json)After cleaning, message or re-install your bot in Teams to ensure the new installation is up-to-date.
Technically, each stored notification installation is bound with one bot. If you are able to check your notification storage, its bot field should match the bot you are running (E.g., the bot id contains the same GUID).
Notification target connections are stored in the persistence storage. If you are using the default local file storage, Azure Web App and Azure Functions will clean up the local file when restart / redeploy.
It's recommended to use your own shared storage for production environment. See Customize Storage.
Or, as a workaround, after restart / redeploy, uninstall the bot from Teams, then re-install it to re-add connections to the storage.
There are Microsoft Graph APIs to list apps installed in a team / group / chat. So it may require you to iterate all your teams / groups / chats to get all the targets a certain app is installed in.
In the notification project, it uses persistence storage to store installation targets. See How Notification Works for more information.
If azurite exits due to port in use, you can specify another listening port and update the connection string of AzureWebJobsStorage in bot/local.settings.json
Microsoft Teams Framework (TeamsFx) supports two major ways to help you send notifications from your system to Teams by creating a Teams Bot Application or Teams Incoming Webhook.
Here's the comparison of the two approaches to help you make the decision.
| Teams Bot App | Teams Incoming Webhook | |
|---|---|---|
| Able to message individual person | Yes | No |
| Able to message group chat | Yes | No |
| Able to message public channel | Yes | Yes |
| Able to message private channel | No | Yes |
| Able to send card message | Yes | Yes |
| Able to send welcome message | Yes | No |
| Able to retrieve Teams context | Yes | No |
| Require installation step on Teams | Yes | No |
| Require Azure resource | Azure Bot Service | No |
Incoming Webhooks help in posting messages from apps to Teams. If Incoming Webhooks are enabled for a team in any channel, it exposes the HTTPS endpoint, which accepts correctly formatted JSON and inserts the messages into that channel. For example, you can create an Incoming Webhook in your DevOps channel, configure your build, and simultaneously deploy and monitor services to send alerts.
Teams Framework has built a sample that walks you through:
- How to create an incoming webhook in Teams.
- How to send notifications using incoming webhooks with adaptive cards.
Build Custom Engine Copilots
- Build a basic AI chatbot for Teams
- Build an AI agent chatbot for Teams
- Expand AI bot's knowledge with your content
Scenario-based Tutorials
- Send notifications to Teams
- Respond to chat commands in Teams
- Respond to card actions in Teams
- Embed a dashboard canvas in Teams
Extend your app across Microsoft 365
- Teams tabs in Microsoft 365 and Outlook
- Teams message extension for Outlook
- Add Outlook Add-in to a Teams app
App settings and Microsoft Entra Apps
- Manage Application settings with Microsoft 365 Agents Toolkit
- Manage Microsoft Entra Application Registration with Microsoft 365 Agents Toolkit
- Use an existing Microsoft Entra app
- Use a multi-tenant Microsoft Entra app
Configure multiple capabilities
- How to configure Tab capability within your Teams app
- How to configure Bot capability within your Teams app
- How to configure Message Extension capability within your Teams app
Add Authentication to your app
- How to add single sign on in Microsoft 365 Agents Toolkit for Visual Studio Code
- How to enable Single Sign-on in Microsoft 365 Agents Toolkit for Visual Studio
Connect to cloud resources
- How to integrate Azure Functions with your Teams app
- How to integrate Azure API Management
- Integrate with Azure SQL Database
- Integrate with Azure Key Vault
Deploy apps to production