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.

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.

Prerequisites

  • Understand bot basics.
  • A copy of the proactive messages sample in either C# or JavaScript. This 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);
}

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

The conversation reference has a conversation property that describes the conversation in which the activity exists. The conversation has a user property that lists the users participating in the conversation, and a service URL property that channels use to denote the URL where replies to the current activity may be sent. A valid conversation reference is needed to send proactive messages to users.

Send proactive message

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

  1. Retrieve the reference for the conversation to which to send the proactive message.
  2. Call 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, use the turn context to send the proactive message.

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)
    {
        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 in C# and JS on GitHub.

Avoiding 401 "Unauthorized" Errors

By default, the BotBuilder 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 by using:

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