Sending Notifications in Microsoft Teams apps

For my Microsoft Teams apps I recently added support for Teams-native notifications. This allows me to conveniently send notifications through Microsoft Teams without having to manage some kind of push service myself.

These notifications can be sent through Microsoft Graph which I’m doing using the On-Behalf-Of-Flow, meaning this only works when a user with the required permissions is performing an action that will trigger a notification.

Here’s how I did it:

App Registration & Scopes

First, let’s update our existing EntraID App Registration (each Microsoft Teams app needs one) with the required API permissions in the Azure Portal. Add TeamsActivity.Send as a delegated Microsoft Graph permission.

Now add the new scope to your frontend code where the login flow is started. I’m using the Microsoft 365 Agents Toolkit (formerly known as Teams Toolkit or TeamsFx), so for me it looks something like this:

const scopes = ["User.ReadBasic.All", "People.Read", "TeamsActivity.Send"];
await teamsUserCredential!.login(scopes);

Creating the Notification Payload

The biggest challenge I’ve faced was creating the correct URL so whenever the user clicks a notification the correct app is opened. This is tricky because the same tab app could be a personal app (in the Teams sidebar) or part of a channel or chat.

For personal apps, it’s rather straight forward how to build the URL since we get the teamsAppId from our app manifest.

https://teams.microsoft.com/l/entity/${teamsAppId}/index

For chat or channel apps, it gets a bit more complicated:

https://teams.microsoft.com/l/entity/${appId}/${pageId}?context=${contextParam}&tenantId=${tenantId}

where contextParam is the stringified channel or chatId:

const contextParam = encodeURIComponent(JSON.stringify({ channelId })); // or chatId

These values can be gathered from the TeamsJs SDK in the frontend. I’m applying them as headers to all API requests so I can get them when needed in the backend.

import { app } from "@microsoft/teams-js";
app
  .initialize()
  .then(async () => {
    const context = await app.getContext();
    axios.defaults.headers.common["appid"] = context.app.appId?.toString();
    
    axios.defaults.headers.common["pageid"] = context.page.id;;
    
    if (context.channel) {
      axios.defaults.headers.common["channel"] = context.channel?.id;
    }

    if (context.chat) {
      axios.defaults.headers.common["chat"] = context.chat?.id;
    }
}

Sending the Notification

With the webUrl assembled we can create our activityNotification and send it through the Graph SDK:

const activityNotification = {
  topic: {
    source: "text",
    value: "Third line",
    webUrl: webUrl
  },
  activityType: "systemDefault",
  previewText: {
    content: "Second Line"
  },
  recipient: {
    "@odata.type": "microsoft.graph.aadUserNotificationRecipient",
    userId: receiverUserId
  },
  templateParameters: [
    {
      name: "systemDefaultText",
      value: "First Line"
    }
  ]
};

await graphClient
  .api(`/users/${receiverUserId}/teamwork/sendActivityNotification`)
  .post(activityNotification);

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.