Notification bot in Teams
Microsoft Teams Toolkit enables you to build applications that capture events and send them as notifications to a personal, group chat, or a channel in Teams. You can send notifications as plain text or Adaptive Cards. The notification bot template creates an app that sends a message to Teams with Adaptive Cards triggered by HTTP post request.
The app template is built using the TeamsFx SDK, which provides a simple set of functions over Microsoft Bot Framework to implement your requirement. For example, in a scenario where a travel agency builds an app in Teams for their customers to keep them up-to-date with the weather forecast. In the following diagram you can see a Teams app that sends notification to the travelers about the destination weather forecast:
You can create notification bot in other scenarios, such as a notification can be sent in teams DevOps channel if there's a build failure.
Advantages
- Facilitates notifications to a personal, group chat, and in a channel, using APIs from TeamsFx SDK.
- Enhances user experience by customizing notification with an Adaptive Card.
- Provides multiple mechanisms to trigger notifications such as HTTP and schedule timer trigger with Azure Functions.
Note
Bot application needs to be installed with the corresponding scope before sending notification.
Notification based on events
Bot Framework SDK provides the functionality to proactively message in Teams. TeamsFx SDK provides the functionality to manage bot's conversation references when a bot event is triggered. TeamsFx SDK recognizes the following bot events:
Event | Behavior |
---|---|
The first time you install a bot to a person, group, or Team. | Add the target conversation reference to the storage. |
When the bot is uninstalled from a person, group, or Team. | Remove the target conversation reference from the storage. |
When the team installed by bot is deleted. | Remove the target conversation reference from the storage. |
When the team installed by bot is restored. | Add the target conversation reference to the storage. |
When the bot sends messages. | When the target conversation reference doesn't exist, add it to the storage. |
When you send notifications, TeamsFx SDK creates a new conversation from the selected conversation reference, and then sends a message. For advanced usage, you can directly access the conversation reference to execute your own bot logic:
// 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...
});
}
Notification bot installation
A notification bot needs to be installed into a team, or a group chat, or as personal app, depending on the required scope. You need to select the installation target before adding the bot to your app.
For more install options, see configure default install options.
Customize notification
You can make the following customizations to extend the notification template to fit your business need:
- Customize the trigger point from event source
- Customize the notification content
- Customize where notifications are sent
Customize the trigger point from event source
You can customize the following triggers:
Restify
based notificationWhen HTTP request is sent to
src/index.js
entry point, the default implementation sends an Adaptive Card to Teams. You can customize this event by modifyingsrc/index.js
. A typical implementation can call an API to retrieve events, data, or both that can send an Adaptive Card as required. You can perform the following to add more triggers:Create a new routing:
server.post("/api/new-trigger", ...)
.Add timer trigger(s) from widely used npm packages, such as cron, node-schedule, or from other packages.
Note
By default Teams Toolkit scaffolds a single
restify
entry point insrc/index.js
.
Azure Functions based notification:
When you select timer trigger, the default implemented Azure Function timer trigger
src/timerTrigger.ts
sends an Adaptive Card every 30 seconds. You can edit the file*Trigger/function.json
to customize theschedule
property. For more information, see Azure Function documentation.When you select
http
trigger, the HTTP request triggers the notification, and the default implementation sends an Adaptive Card to Teams. You can change this event by customizingsrc/*Trigger.ts
. This implementation can call an API to retrieve events, data, or both, which can send an Adaptive Card as required.
Azure Function triggers:
Event Hub
trigger to send notifications when an event is pushed to Azure Event Hub.Cosmos DB
trigger to send notifications when a Cosmos document is created or updated.
For more information on support triggers, see Azure Functions support triggers.
Customize the notification content
The file src/adaptiveCards/notification-default.json
defines the default Adaptive Card. You can use the Adaptive Card designer to help visually design your Adaptive Card UI. The src/cardModels.ts
defines a data structure that is used to load data for the Adaptive Card. The binding between the card model and the Adaptive Card is done by matching name such as CardData.title
maps to ${title}
in the Adaptive Card. You can add, edit, or remove properties and their bindings to customize the Adaptive Card as required.
You can also add new cards if needed. For more information on how to build different types of Adaptive Cards with a list or table of dynamic contents using ColumnSet
and FactSet
, see Adaptive Card notification sample.
Customize where notifications are sent
You can customize sending the notification to the following targets:
Notifications to a personal chat:
// 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(...); } }
// 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(...); } }
Notifications to a group chat:
// 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(...); } } }
// 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(...); } } }
Notifications to a channel:
// 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(...); } } }
// 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(...); } } }
Notifications to a specific channel:
// find the first channel when the predicate is true. const channel = await bot.notification.findChannel(c => Promise.resolve(c.info.name === "MyChannelName")); // send adaptive card to the specific channel. await channel?.sendAdaptiveCard(...);
Note
To prevent an undefined output, ensure that you install the bot app in the General channel of a Team.
Notifications to a specific person:
// find the first person when the predicate is true. const member = await bot.notification.findMember(m => Promise.resolve(m.account.name === "Bob")); // send adaptive card to the specific person. await member?.sendAdaptiveCard(...);
Note
To prevent an undefined output and a missing notification, you need to include the specific person in notification installation scope.
Customize initialization
You need to create ConversationBot
to send notification.
Note
The code is generated in project.
/** Javascript/Typescript: bot/src/internal/initialize.*s **/
const bot = new ConversationBot({
// The bot id and password to create BotFrameworkAdapter.
// See https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 to learn more about adapters.
adapterConfig: {
appId: process.env.BOT_ID,
appPassword: process.env.BOT_PASSWORD,
},
// Enable notification
notification: {
enabled: true,
},
});
Customize adapter
You can customize by creating your own adapter, or customize the adapter after initialization. Following is the code sample for creating your adapter:
// 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 = ...
Add storage
Storage can be used to implement notification connections. You can add your own storage with the help of following code sample:
// 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://learn.microsoft.com/en-us/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 to learn more about adapters.
adapterConfig: {
appId: process.env.BOT_ID,
appPassword: process.env.BOT_PASSWORD,
},
// Enable notification
notification: {
enabled: true,
storage: myStorage,
},
});
If storage isn't provided, you can use a default local file storage, which stores notification connections into:
.notification.localstore.json
if running locally.${process.env.TEMP}/.notification.localstore.json
, ifprocess.env.RUNNING_ON_AZURE
is set to 1.
For sample implementation to use Azure blob storage, see add notification storage implementation sample.
Note
It's recommended to use your own shared storage for production environment.
Add authentication for notification API
If you select HTTP trigger, the scaffolded notification API doesn't have authentication or authorization enabled. Ensure that you add authentication or authorization for the API before using it for production. You can perform either of the following:
Use an API key. You can use function access keys, if you select Azure Functions to host your notification bot.
Use an access token issued by Azure Active Directory (Azure AD). For more information, see Configure SSO for your bot in Azure AD.
There can be more authentication or authorization solutions for an API, you can select as required.
Connect to existing APIs
If you don't have the required SDK, and want to invoke external APIs in your code. The Teams: Connect to an API
command in Microsoft Visual Studio Code Teams Toolkit extension, or teamsfx add api-connection
command in TeamsFx CLI can be used to bootstrap code to call target APIs. For more information, see configure API connection.
Teams bot application or Teams Incoming Webhook
TeamsFx supports two ways to help you send notifications from your system to Teams:
- Create a Teams bot app.
- Create Teams Incoming Webhook.
In the following table, you can see the comparison of the two different ways:
Teams bot app | Teams Incoming Webhook | |
---|---|---|
Message individual person | ✔️ | ❌ |
Message group chat | ✔️ | ❌ |
Message public channel | ✔️ | ✔️ |
Message private channel | ❌ | ✔️ |
Send card message | ✔️ | ✔️ |
Send welcome message | ✔️ | ❌ |
Retrieve Teams context | ✔️ | ❌ |
Require installation steps in Teams | ✔️ | ❌ |
Require Azure resource | Azure Bot Service | ❌ |
Incoming Webhook notification
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. TeamsFx provides you with an Incoming Webhook notification sample that helps you:
- Create an Incoming Webhook in Teams.
- Send notifications using Incoming Webhooks with Adaptive Cards.
FAQ
Why is the notification installations empty even though the bot app is installed in Teams?
Teams sends an event only at the first installation. If the bot app is already installed before your notification bot service is launched, the installation event, either didn't reach the bot service, or is omitted.
You can resolve this in the following ways:
- Send a message to your personal bot or mention your bot in group chat, or channel, which helps you to reach the bot service again with correct installation information.
- Uninstall the bot app from Teams then redebug or relaunch it. You can resend the installation event to bot service.
Notification target connections are stored in the persistence storage. If you're using the default local file storage, all installations are stored under bot/.notification.localstore.json
.
Note
For more information to add your own storage, see add storage.
Why Bad Request or Bad Argument error occurs when sending notification?
If the notification installation doesn't match the bot ID or password, you can get a Failed to decrypt conversation ID error. One possible cause for this error is that the bot ID or password is changed due to cleaning local state or reprovisioning.
You can resolve this by cleaning your notification storage. After cleaning, notify in Teams to reinstall your bot, and ensure that the new installation is up-to-date. Each stored notification installation is bound with one bot. If you're able to check your notification storage, its bot field should match the bot you're running such as the bot ID with the same GUID.
Note
In case of local storage the default location is .notification.localstore.json
.
Why notification target is lost after restarting or redeploying the bot app?
Notification target connections are stored in the persistence storage. If you're using the default local file storage, Azure web app and Azure Functions clean up the local file during a restart or redeploy. You can also uninstall the bot from Teams, then install it to again add connections to the storage. It's recommended to use your own shared storage for production environment.
Why is undefined error returned when using the API findChannel()?
You can encounter an undefined error, when the bot app is installed into other channels instead of the General channel. To fix this error, you can uninstall the bot app from Teams and redebug and relaunch it. After you've redebug and relaunched, ensure that the bot app is installed into the General channel.
Can I know all the targets where my bot is installed in and out of the notification project?
There are Microsoft Graph APIs to list apps installed in a team, group, or chat. If required you need to iterate your team, group, or chat into an installed app to be targeted. In the notification project, it uses persistence storage to store installation targets. For more information, see notification based on events.
How to customize the Azurite listening ports?
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
.
How to extend my notification bot to support command and response?
The command and response adds the ability for your bot to capture the commands sent in a Teams message and respond with Adaptive Cards. See the steps to add command and response.
How to extend my notification bot by adding workflow bot Adaptive Card actions?
You can extend your notification bot by defining actions and use workflow bot to return an Adaptive Card in response to the action. To add an Adaptive Card in notification bot, see the steps to add card action.
Step-by-step guide
Follow the step-by-step guide to build Teams notification bot.
See also
Feedback
Submit and view feedback for