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:

weather forecast sample notification scenario

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.

Back to top

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.

new notification event sample

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...
    });
}

Back to top

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.

add installation scope

For more install options, see configure default install options.

Back to top

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

You can customize the following triggers:

  • Restify based notification

    When 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 modifying src/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 in src/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 the schedule property. For more information, see Azure Function documentation.

      sample of timer triggered notification

    • 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 customizing src/*Trigger.ts. This implementation can call an API to retrieve events, data, or both, which can send an Adaptive Card as required.

      sample of HTTP triggered notification

  • 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.

Back to top

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,
    },
});

Back to top

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 = ...

Back to top

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, if process.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.

Back to top

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:

There can be more authentication or authorization solutions for an API, you can select as required.

Back to top

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:

Back to top

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.


Back to top

Step-by-step guide

Follow the step-by-step guide to build Teams notification bot.

See also