Monday, 24 February 2020

Microsoft Bot Framework v4: Send proactive messages to Teams channels and users

What is a Bot Framework Proactive message?

Usually, for starting a conversation with a Microsoft Teams bot, the user has to initiate the conversation either by sending a personal message to the bot, or by mentioning the bot in a Teams channel or by invoking a messaging extension.

With proactive messaging, the bot can start a conversation with a user (or in a Teams channel) without anyone having to invoke the bot first. This conversation can be started based on any custom logic fit for your application e.g. The occurrence of  an external event, or a webhook getting triggered or even on a scheduled periodical basis.

More about Bot Framework proactive messages here: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp

I should mention that the bot will first need to be installed in the Team, if you want to send a proactive message to a Teams channel, or to the users who are part of that team.



How to send proactive messages?

So in this post, let's look at a few code samples which make it very easy for our Teams Bot to send proactive messages to users or channels in a Team.

These code samples are based on a standalone .NET Core console app. This is mainly to show that as long as you have the necessary information, your code doesn't need to be running under the Bot messaging endpoint. Once you have the information from the Bot messaging endpoint, the proactive messaging code can run from any platform e.g. from an Azure Function.

If you have a look at the Bot Framework code samples published by Microsoft, they all use code which is running under the messaging endpoint. This initially led me to believe that even for proactive messaging, the code should live under the same endpoint. But as we will see in this post, that is not the case.

What are the prerequisites?

As mentioned before, our bot will need to be installed in a Team first. This will allow the bot messaging endpoint to receive the required values from Teams and send it to our proactive messaging code. If the bot is not installed in the Team, you will get a "Forbidden: The bot is not part of the conversation roster" error.

Base URL (serviceUrl)

This is the Url to which our proactive messaging code should send all the requests. This Url is sent by Teams in the Bot payload in the turnContext.Activity.serviceUrl property. For all intents and purposes this url will remain constant but after having a discussion with Microsoft, they have recommended that this url might change (very rarely) and our bot should have the logic for updating the stored base url periodically from the payload sent to the bot. More about the Base Url here: https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#base-uri

Internal Team Id

This is the internal team id which is not the same as the Office 365 Group Id. The internal team id is in the following format: 19:bf2b184e7cbb4f9f9ca1b47f755cd943@thread.skype

You can get the internal team id from the Bot payload in the channelData.team.id property. You can also get this id through the Microsoft Graph API: https://docs.microsoft.com/en-us/graph/api/resources/team?view=graph-rest-1.0#properties

Channel Id

If we want our bot to post to a specific channel in a Team, then we will need the channel id as well. The format for the channel id is exactly the same as the internal team id. Also, you can get the channel id from the bot payload as well as the Microsoft Graph api: https://docs.microsoft.com/en-us/graph/api/resources/channel?view=graph-rest-1.0#properties

Internal Teams User Id

This would only be needed if you want to send a proactive personal message to a specific user. For all users in a team, Teams maintains an encoded user id so that only bots installed in a team are able to message users. To get this user id, our bot needs to call the conversations/{conversationId}/members REST API endpoint. Fortunately for us the Bot Framework wraps this call in a handy SDK method as shown in the third code sample below.

So once we have all the required values from the Bot messaging endpoint, we are able to send proactive messages. For this sample code, I am using the Microsoft.Bot.Builder v4.7.2
https://www.nuget.org/packages/Microsoft.Bot.Builder/

1) Post a proactive message in a Teams channel



(click to zoom)

2) Post a proactive message in a Teams channel and mention a user in it



(click to zoom)


3) Post a proactive personal message to a user


(click to zoom)

Hope you found the post useful!

6 comments:

Unknown said...

Hi Vardhaman, Your blog is nice and very helpful. One main question I have is that

How/where can I get the teams internal id for sending proactive messages to a user on 1 on 1 chat?

Vardhaman Deshpande said...

Hello there,

Teams internal id can be fetched from the Bot Payload or from the Graph API. Have a look the the "Internal Team Id" section in the start of the blog post.

Thanks,
Vardhaman

Z. Saad said...

Brilliant!

Piecing this together with MS docs was an absolute mess. Half way there, found this post and completed the task right away.

Kudos!

Unknown said...

var user = teamMembers
.Select(channelAccount => JObject.FromObject(channelAccount).ToObject())
.First(user => user.UserPrincipalName == mentionUserPrincipalName); [TeamsChannelAccount from namespace Microsoft.Bot.Schema]

var personalMessageActivity = MessageFactory.Text($"Personal message from the Bot!");

var conversationParameters = new ConversationParameters()[These are from namespace Microsoft.Bot.Schema]
{
ChannelData = new TeamsChannelData
{
Tenant = new TenantInfo
{
Id = tenantId,
}
},
Members = new List() { user }
};

var response = await connectorClient.Conversations.CreateConversationAsync(conversationParameters);[CreateConversationAsync method looks for paramters of type Microsoft.Bot.Connector.

Question:-How to Resolve the conflict between Microsoft.Bot.Connector and using Microsoft.Bot.Schema.Teams.

Vardhaman Deshpande said...

Hi Unknown, just checked with Bot Framework 4.9 and CreateConversationAsync does expect conversationParameters from Microsoft.Bot.Schema

Unknown said...

That is true but in the sentence
var connectorClient = new ConnectorClient(new Uri(serviceUrl), new MicrosoftAppCredentials(botClientID, botClientSecret)); we are connecting to Microsoft.bot.connector and in line await connectorClient.Conversations.SendToConversationAsync(response.Id, personalMessageActivity); we are using the same connectorClient to call the SendToConversationAsync method . Hence it is expecting paramters of type Microsoft.Bot.Connector in CreateConversationAsync method