2

I have an Azure Bot installed in my organization through Teams. Interactions with the bot are properly functioning.

We have a scenario where we need to send notifications to our users from an external process (C# app running in Azure).

I attempted to use the Bot Framework REST API to create a conversation with a user in order to then message them with the notification as outlined here

This scenario does not work as I cannot get an access token for a bot that is not using the global Bot Framework Tenant. Our Bot is installed on our Azure tenant as a SingleTenant BotType so I get the following error:

 Application with identifier 'BOT-APP-ID' was not found in the directory 'Bot Framework'

I've poured over various options including DirectLine channels, BotConnector SDK and Power Automate - but nothing seems to fit with my needs.

Is there a way to use a Rest API against the Bot we have installed to create conversations and send messages? This would be ideal as then I can initiate these notifications directly from the events that triggered them.

UPDATE

Hilton's answer below brought some much needed clarity to the situation. Ultimately I had to deploy a new Bot set to use the Multitenant BotType.

This configured an AppRegistration tied to the Bot set to use MultiTenant as well.

Additionally in the WebApp where you have your api/messages endpoint hosted you have to include a property called MicrosoftAppType and set that to MultiTenant as well. Here is the full configuration required for the WebApp:

MicrosoftAppId: [The AppId of the AppRegistration]
MicrosoftAppPassword: [The Secret of the AppRegistration]
MicrosoftAppTenantId: [Your Tenant Id]
MicrosoftAppType: MultiTenant

To capture the ConversationId, ServiceUrl and UserId I added the following to the OnMembersAddedAsync

foreach (var member in membersAdded)
{
    if (!string.IsNullOrEmpty(member.AadObjectId))
    {
        //This is a user in our AAD. Store the conversation id reference:
        var userId = member.AadObjectId;
        var serviceUrl = turnContext.Activity.ServiceUrl;
        var conversationId = turnContext.Activity.Conversation.Id;

        //Save variables to your state store here...
    }
    else
    {
         // This is likely a test outside of Teams 
         //var userId = member.Id; //<-- Not used in my scenario
    }
}

Then in my external app (A Console in this example) I can use those variables (along with the AppRegistration credentials) to create a Bot ConnectorClient and update the conversation:

using Microsoft.Bot.Connector;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;


var botAppId = "[BOT_ID/APP_REG_ID]";
var botAppKey = "[APP_REG_SECRET]";
var conversationId = "[CONVERSATION_ID]";
var serviceUrl = "[SERVICE_URL]";

MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);
var connector = new Microsoft.Bot.Connector.ConnectorClient(new Uri(serviceUrl), botAppId, botAppKey);

var activity = new Activity()
{
    Text = "Proactively saying **Hello**",
    Type = ActivityTypes.Message,
    Conversation = new ConversationAccount(false, "personal", conversationId)
};

try
{
    var result = await connector.Conversations.SendToConversationAsync(conversationId, activity);

    Console.WriteLine("Notification sent!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
INNVTV
  • 3,155
  • 7
  • 37
  • 71

2 Answers2

2

Proactive messaging is definitely what you're looking for, but there are a few important things to be aware of. Here is a sample https://github.com/pnp/teams-dev-samples/tree/main/samples/bot-proactive-messaging that will hopefully be useful - I included both a C# and a Node version as well as some links to further reading, and here is a link to a video session where I talk more about the concept: https://www.youtube.com/watch?v=mM7-fYdcJhw&t=1398s.

In simple terms, remember that Bot Framework can be used in many contexts, Teams is just one of those. Importantly, unlike other contexts, when you're in Teams there is no concept of "creating" a conversation with the user. There is only ever a single "conversation", and you are basically "continuing" the conversation. As a result, you want to call continueConversation. In the same sample I linked above, here is the relevant line. Under the covers, this is indeed calling a REST API, but wrapped like this it's easier.

As the sample shows, however, because you can't start a conversation, and can only continue one, you need to make sure you have the conversation context already, and that may also mean ensuring that the user has the bot installed already into the personal context (which is what actually does start the conversation). Here is where that happens in the sample.

If your users have the bot installed already, then its just a case of storing the conversation context like I show in the sample. If not, and you want to learn how you can pre-install the bot, see this question: Proactively Install / Push Apps in Teams for Multiple Users

Hilton Giesenow
  • 9,809
  • 2
  • 10
  • 24
  • Thanks Hilton. So it looks what I need to do is the following: 1. Capture and store the ConversationId to a database for each user when OnMembersAddedAsync is called. 2. Expose an endpoint in my Bot web service to allow for me to send messages to those conversations. 3. Provide a way for my external process to lookup the ConversationId by UserId so it can post to the endpoint I created in step 2. I'll move forward with this and let you know how it goes unless I am missing anything! – INNVTV Mar 21 '22 at 11:52
  • Even better: for step 3 all the external service needs to know is the UserId (member.AadObjectId) and the bot can do the lookup when we post the proactive message. – INNVTV Mar 21 '22 at 12:06
  • Yes that's pretty much on target, but you don't need to expose the endpoint in your bot web app. Even though the proactive message "comes from" your bot, the actual call can come from another application entirely. For instance, your bot could run in an Azure Web App, and you could have an Azure Function that does the proactive message. To the user, it looks like it came "from" the bot – Hilton Giesenow Mar 21 '22 at 12:44
  • In terms of comment 2, you could technically use anything you want to look up the user in your database, but yes, AadObjectId is usually the best, as Teams presents it to you everywhere. – Hilton Giesenow Mar 21 '22 at 12:45
  • Question: When deployed to production what will serviceUrl be since I won't be using NGrok? I'm a little lost on that one. – INNVTV Mar 21 '22 at 12:45
  • "serviceUrl", for the user context, depends on where the user's account is stored in M365 - it can in theory differ per user even within the same tenant, and in theory it can even change over time(!), but I've not seen it do that. It's got nothing to do with where you host your bot though... – Hilton Giesenow Mar 21 '22 at 13:47
  • Sorry Hilton, I was referring to the serviceUrl that is used to create the ConnectorClient along with the MicrosoftAppId & MicrosoftAppPassword. How do I use ConnectorClient in production? It seems to require a URI for my bot but what is that URI and/or how do I get it? – INNVTV Mar 21 '22 at 15:38
  • yes, it's the same one, and you need to use the one presented by the user when you store their details. It will look something like 'https://smba.trafficmanager.net/emea' or similar. That's (kind of) per user, like I said, and is totally different from the url your bot itself is hosted on - that can be anything you like, based on the hosting you choose – Hilton Giesenow Mar 21 '22 at 16:18
  • Aha! OK. I see it now! Obviously it is localhost while I am using the emulator against my local build but in production it uses a URL that is tied to the user/tenant. Since I like to know how my sausage is made I have to ask... Is this trafficmanager url something that existed before Bot Framework or is it a provisioned resource tied to it that every M365 tenant gets so it can plug into Bots? I guess I am curious what else this serviceUrl is used for. – INNVTV Mar 21 '22 at 16:29
  • Oh, and the fact that it could change is alarming. Is there a way to monitor that within my tenant? – INNVTV Mar 21 '22 at 16:31
  • I think you're getting crossed again between the endpoint for your bot itself, and the service url. This blog post might explain it better, especially if you want to see more 'sausageworks': https://hilton.giesenow.com/how-bot-calls-actually-work . To put it differently: your bot's endpoint is something YOU choose. The serviceUrl is something you're GIVEN for that particular user/channel/group chat. – Hilton Giesenow Mar 21 '22 at 19:15
  • separately, the trafficmanager/service url is actually something outside of Teams altogether - it's part of the underlying Microsoft Bot Framework, which is used for bots in many scenarios. As an example, you can build a Slack or facebook bot using Bot Framework as well. – Hilton Giesenow Mar 21 '22 at 19:16
  • Regarding the serviceurl changing, I've not actually experienced it every happening, but I just recall hearing that MS reserves the right to do so. Not sure if there would ever be a notification -should- it ever occur, but I think it might be worrying too much. – Hilton Giesenow Mar 21 '22 at 19:17
  • Well it's come full circle. I am storing the UserId, ConversationId and ServiceUrl and when I use those with the AppId and AppPassword in the ConnectorClient to respond to the existing conversation I get the "Application with identifier 'BOT-APP-ID' was not found in the directory 'Bot Framework'" just as before! Since I am not in the global Bot Framework tenant how can I properly make this call? – INNVTV Mar 21 '22 at 20:11
  • When I look at the connector the Credentials.OAuthEndpoint is set to 'https://login.microsoftonline.com/botframework.com' and OAuthScope to 'https://api.botframework.com' and they cannot be changed. – INNVTV Mar 21 '22 at 20:20
  • haha, back where we started. Hopefully better informed at least :-). Here's something important to check - what ID are you using for your bot as the Application Id? In a Teams app+bot, there are two IDs at play - one is the BOT itself, the other is the "Teams" application id, in the manifest file, which is the manifestation in Teams for your bot and any other aspects of the the project (e.g. a Tab, Connector, etc. etc.) that your project might contain. Perhaps you're using the Teams App Id instead of the Bot ID (the one that appears in the Azure Portal for your bot)? – Hilton Giesenow Mar 22 '22 at 07:33
  • Definitely better informed! You have been a great help! I am for sure using the MicrosoftAppId that my Azure Bot is using (which is also an AppRegistration in AAD). This is the same AppId that the WebApp running the /api/messages endpoint is using. Everything is running in my tenant (I do not have any bots registered in dev.botframework.com) and I think this might be the issue for authenticating? I am able to respond to users with interactive dialogs so I know I have everything set up properly.... – INNVTV Mar 22 '22 at 11:08
  • 1
    When setting up the bot initially I had the option to use "User-Assigned Managed Identity", "Multi-Tenant" or "Single-Tenant". I went with "Single-Tenant" since this will ONLY be used in Teams and within the organization. I suspect this also the reason the bot is not able to be authenticated as part of the "Bot Framework" tenant? But surely there is a way to send proactive messages still? Like I said I can respond to users with dialogs and echos so everything is flowing through as expected otherwise. – INNVTV Mar 22 '22 at 11:11
  • @INNVTV - Could you please confirm is your issue resolved with above suggestions or still looking for any help – Nivedipa-MSFT Mar 23 '22 at 15:49
  • Issue is resolved - OP updated original question with the solution details – Hilton Giesenow Mar 23 '22 at 17:29
  • @Nivedipa-MSFT Confirmed issue is resolved. As Hilton mentioned I have updated my answer to reflect the details. – INNVTV Mar 24 '22 at 17:01
1

Hilton's answer is largely correct, however, it's worth noting a couple of additional points and addendums...

Firstly, you can start a conversation via a Teams bot (which will return the existing conversation details if it already exists) and means your notification API endpoint can accept upn instead of the rather more cryptic "conversationId" - it's not the easiest thing to do but it is possible using the CreateConversationAsync method of the adapter. The challenge is getting the "internal" id for the user you are sending the message to as this isn't readily available and is required for the ConversationReference object.

This user id can be retrieved by getting the reference to the 1:1 chat between the user and the bot via the installedApps/chat Graph call and inspecting the "members" (for which there would only be two... the user and the app). Then you can create a ConversationReference object which in turn allows you to call CreateConversationAsync and if it's not yet installed proactively installing the app for the user (assuming consented scopes) - here is a C# sample and a Node sample that demonstrates this entire flow.

Note: for proactive installation to work, the app really needs to be in the public app store or the org store because the Teams App Id (specified in the manifest) is randomised for side-loaded apps and therefore isn't easily discoverable.

Secondly, although the service url now requires a geo (https://smba.trafficmanager.net/{emea,uk,apac,etc...}) in my testing you can use any valid value and the activities are routed correctly... now I couldn't say for sure whether this will be the case in the future and obviously you'd need to look into whether this is acceptable from a data flow point of view but I will typically use a preconfigured "fallback" serviceurl and then cache each user's actual serviceurl when I receive a turn context - I also just store this in-memory because it doesn't really matter if its lost as I will build up that dictionary over time anyway.

I have this conversation a lot so wrote about it here: https://hackandchat.com/teams-proactive-messaging/ - this also covers proactive installation too for when you want to notify a user that hasn't yet installed the app.

Scott Perham
  • 2,410
  • 1
  • 10
  • 20
  • Hi Scott. Neat technique! I'll give it a try. But before that, I'd like to ask how you would compare the options to send users a notification: proactive message / Activity feed notification / incoming webhook. I haven't looked into these options myself but at first glance, the proactive message approach has its limits (eg can't send to whom doesn't install the app yet; difficult to get the mapped user id from the true user id or email and etc). Would appreciate your insights! – John Jun 18 '22 at 17:05