Send proactive notifications to users

APPLIES TO: yesSDK v4 no SDK v3

Typically, each message that a bot sends to the user directly relates to the user's prior input. In some cases, a bot may need to send the user a message that is not directly related to the current topic of conversation or to the last message the user sent. These types of messages are called proactive messages.

Proactive messages can be useful in a variety of scenarios. For example, if the user has previously asked the bot to monitor the price of a product, the bot can alert the user if the price of the product has dropped by 20%. Or, if a bot requires some time to compile a response to the user's question, it may inform the user of the delay and allow the conversation to continue in the meantime. When the bot finishes compiling the response to the question, it will share that information with the user.

Requirements

Before you can send a proactive message, your bot needs a conversation reference. Your bot can retrieve the conversation reference from any activity it has received from the user, but this typically requires the user to interact with the bot at least once before the bot can send a proactive message.

Many channels prohibit a bot from messaging a user unless the user has messaged the bot at least once. Some channels allow exceptions. For instance, the Teams channel allows your bot to send a proactive (or 1-on-1) message to individuals in an already established group conversation that includes the bot.

More information about proactive messages in Teams can be found in these resources:

Prerequisites

  • Understand bot basics.
  • A copy of the proactive messages sample in C#, JavaScript, or Python. The sample is used to explain proactive messaging in this article.

About the proactive sample

The sample has a bot and an additional controller that is used to send proactive messages to the bot, as shown in the following illustration.

proactive bot

Retrieve and store conversation reference

When the emulator connects to the bot, the bot receives two conversation update activities. In the bot's conversation update activity handler, the conversation reference is retrieved and stored in a dictionary as shown below.

Bots\ProactiveBot.cs

private void AddConversationReference(Activity activity)
{
    var conversationReference = activity.GetConversationReference();
    _conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
}

protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
    AddConversationReference(turnContext.Activity as Activity);

    return base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}

The conversation reference includes a conversation property that describes the conversation in which the activity exists. The conversation includes a user property that lists the users participating in the conversation, and a service URL property that indicates where replies to the current activity may be sent. A valid conversation reference is needed to send proactive messages to users. (For the Teams channel, the service URL maps to a regionalized server.)

Note

In a real-world scenario you would persist conversation references in a database instead of using an object in memory.

Send proactive message

The second controller, the notify controller, is responsible for sending the proactive message to the bot. It uses the following steps to generate a proactive message.

  1. Retrieves the reference for the conversation to which to send the proactive message.
  2. Calls the adapter's continue conversation method, providing the conversation reference and the turn handler delegate to use. (The continue conversation method generates a turn context for the referenced conversation and then calls the specified turn handler delegate.)
  3. In the delegate, uses the turn context to send the proactive message.

Note

While each channel should use a stable service URL, the URL can change over time. For more information about the service URL, see the Basic activity structure and Service URL sections of the Bot Framework Activity Schema.

If the service URL changes, previous conversation references will no longer be valid and calls to continue conversation will generate an error or exception. In this case, your bot will need to acquire a new conversation reference for the user before it can send proactive messages again.

Controllers\NotifyController .cs

Each time the bot's notify page is requested, the notify controller retrieves the conversation references from the dictionary. The controller then uses the ContinueConversationAsync and BotCallback methods to send the proactive message.

[Route("api/notify")]
[ApiController]
public class NotifyController : ControllerBase
{
    private readonly IBotFrameworkHttpAdapter _adapter;
    private readonly string _appId;
    private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;

    public NotifyController(IBotFrameworkHttpAdapter adapter, IConfiguration configuration, ConcurrentDictionary<string, ConversationReference> conversationReferences)
    {
        _adapter = adapter;
        _conversationReferences = conversationReferences;
        _appId = configuration["MicrosoftAppId"];

        // If the channel is the Emulator, and authentication is not in use,
        // the AppId will be null.  We generate a random AppId for this case only.
        // This is not required for production, since the AppId will have a value.
        if (string.IsNullOrEmpty(_appId))
        {
            _appId = Guid.NewGuid().ToString(); //if no AppId, use a random Guid
        }
    }

    public async Task<IActionResult> Get()
    {
        foreach (var conversationReference in _conversationReferences.Values)
        {
            await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, BotCallback, default(CancellationToken));
        }
        
        // Let the caller know proactive messages have been sent
        return new ContentResult()
        {
            Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
            ContentType = "text/html",
            StatusCode = (int)HttpStatusCode.OK,
        };
    }

    private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
    {
        // If you encounter permission-related errors when sending this message, see
        // https://aka.ms/BotTrustServiceUrl
        await turnContext.SendActivityAsync("proactive hello");
    }
}

To send a proactive message, the adapter requires an app ID for the bot. In a production environment, you can use the bot's app ID. In a local test environment, you can use any GUID. If the bot is not currently assigned an app ID, the notify controller self-generates a placeholder ID to use for the call.

Test your bot

  1. If you have not done so already, install the Bot Framework Emulator.
  2. Run the sample locally on your machine.
  3. Start the emulator and connect to your bot.
  4. Load to your bot's api/notify page. This will generate a proactive message in the emulator.

Additional information

Besides the sample used in this article, additional samples are available on GitHub.

Design considerations

When implementing proactive messages in your bot, don't send several proactive messages within a short amount of time. Some channels enforce restrictions on how frequently a bot can send messages to the user, and will disable the bot if it violates those restrictions.

An ad hoc proactive message is the simplest type of proactive message. The bot simply interjects the message into the conversation whenever it is triggered, without any regard for whether the user is currently engaged in a separate topic of conversation with the bot and will not attempt to change the conversation in any way.

To handle notifications more smoothly, consider other ways to integrate the notification into the conversation flow, such as setting a flag in the conversation state or adding the notification to a queue.

Avoiding 401 "Unauthorized" Errors

By default, the Bot Builder SDK adds a serviceUrl to the list of trusted host names if the incoming request is authenticated by BotAuthentication. They are maintained in an in-memory cache. If your bot is restarted, a user awaiting a proactive message cannot receive it unless they have messaged the bot again after it restarted.

To avoid this, you must manually add the serviceUrl to the list of trusted host names.

MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);

For proactive messaging, serviceUrl is the URL of the channel that the recipient of the proactive message is using and can be found in Activity.ServiceUrl.

You'll want to add the above code just prior to the the code that sends the proactive message. In the Proactive Messages Sample, you would put it in NotifyController.cs just before await turnContext.SendActivityAsync("proactive hello");.

Next steps