Last time I proved I could send a proactive message to myself, but I needed my user ID that I could only get after I’d started a conversation with the bot. For my real-life scenario I need to be able to push notifications to users that haven’t interacted with my bot before.

To get this to work I need to install my Teams app for the other users via Microsoft Graph, and for that to work I need to get my (currently private) app listed in my organisation’s app catalogue.

Listing in the App Catalogue

Back in the App Studio app in Teams, I went back to the Manifest Editor and selected my existing app. On the “Test and distribute” tab I clicked Download to get the zip file for my app.

Next, in the Teams admin site, I went to “Teams apps” > “Manage apps”, clicked “Upload” and selected my zip file. A few seconds later my app appeared in the list:

Getting User IDs

When a user installs my app (or I install it for them), it will automatically start a new conversation with my bot and I can grab the user ID and other details I need.

Last time I used the OnMessageActivityAsync to get the ID when I sent a message. I’m going to switch to use the OnMembersAddedAsync method that’s currently being used to trigger the welcome message so I can get the IDs straight away, without having to wait for the user to do anything. I’m going to use this code to get the values I need:

var teamConversationData = turnContext.Activity.GetChannelData<TeamsChannelData>();
var tenantId = teamConversationData.Tenant.Id;
var serviceUrl = turnContext.Activity.ServiceUrl;

foreach (var member in membersAdded)
    if (member.Id != turnContext.Activity.Recipient.Id)
        var userId = member.Id;
        var username = ((TeamsChannelAccount)member).UserPrincipalName;

        // TODO: Store details

As well as the user ID, tenant ID and service URL that we saw we needed before, I’m also pulling out one more bit of information – the user principal name ( This is so I can target the correct Teams user based on the username we have in D365.

Storing User IDs

Now we’ve got all these details, we need to save them somewhere. We’ll need to use them in future to look up a username from D365 and find the related details to trigger a proactive message. Azure Table Storage seems to fit the bill perfectly for this – simple and virtually free.

All I need to do in Azure is create a new storage account and make a note of the connection string that’s generated for it. Put the connection string into the appsettings.json file in the bot so we can access it later.

Back in Visual Studio we need to add the Microsoft.Azure.Cosmos.Table NuGet package. Although we’re not using Cosmos DB, this is now the supported NuGet package for working with the Table API on both Azure Table Storage and Cosmos DB.

With that added we can define the structure of the table we’re going to use:

public class User : TableEntity
    public User(string username) : base(username, "")

    public User()

    public string UserId { get; set; }

    public string TenantId { get; set; }

    public string ServiceUrl { get; set; }

Now I can now store the user details with:

var connectionString = _config.GetConnectionString("Storage");
var storageAccount = CloudStorageAccount.Parse(connectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference("users");

var user = new User(username)
    UserId = userId,
    TenantId = tenantId,
    ServiceUrl = serviceUrl

var insert = TableOperation.Insert(user);

To test this I uninstalled the app from Teams and re-added it to trigger the OnMembersAddedAsync event again. Unfortunately this didn’t work quite as expected:

After a bit of debugging (I’m not too proud to say that I used the bot equivalent of Console.WriteLine – catching the exception and sending it back to me as a message) I found that the error was coming from this line:

var username = ((TeamsChannelAccount)member).UserPrincipalName;

The member was a ChannelAccount and not a TeamsChannelAccount. To get these Teams-specific extensions I just need to change the base class for my bot from ActivityHandler to TeamsActivityHandler and re-publish it.

With this done I can now uninstall and reinstall the app again to trigger saving my user ID. A quick check in Storage Explorer shows my user details have been stored as expected:

Installing via Microsoft Graph

Because a notification might need to be sent out before a user installs our app manually, we need to push the app out. The recommended (but in-preview) way of doing this is through Microsoft Graph. There’s a sample available to show how to do this, but I haven’t used Microsoft Graph before and I need to do some more work to get this going how I want, so I’ll dig into this further next time.

Leave a Reply

Your email address will not be published. Required fields are marked *

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