'Sending proactive messages from an outside process to organizational users via Teams chat bot

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


Solution 1:[1]

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

Solution 2:[2]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Hilton Giesenow
Solution 2 Scott Perham