Send proactive notifications to users

APPLIES TO: SDK v4

Typically, a bot sends a message to a user directly in response to receiving a message from the user. Occasionally, a bot might need to send a proactive message, a message in response to stimulus not originating from the user.

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

In general, a bot as an application has a few layers:

  • The web application that can accept HTTP requests and specifically supports a messaging endpoint.
  • An adapter that handles connectivity with the channels.
  • A handler for the turn, typically encapsulated in a bot class that handles the conversational reasoning for the bot app.

In response to an incoming message from the user, the app calls the adapter's process activity method, which creates a turn and turn context, calls its middleware pipeline, and then calls the bot's turn handler.

To initiate a proactive message, the bot application needs to be able to receive additional input. The application logic for initiating a proactive message is outside the scope of the SDK. For this sample, a notify endpoint, in addition to a standard messages endpoint, is used to trigger the proactive turn.

In response to a GET request on this notify endpoint, the app calls the adapter's continue conversation method, which behaves similarly to the the process activity method. The continue conversation method:

  • Takes an appropriate conversation reference for the user and the callback method to use for the proactive turn.
  • Creates an event activity and turn context for the proactive turn.
  • Calls the adapter's middleware pipeline.
  • Calls the provided callback method.
  • The turn context uses the conversation reference to send any messages to the user.

The sample has a bot, a messages endpoint, and an additional notify endpoint that is used to send proactive messages to the user, 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 user. 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. Here, the delegate is defined on the notify controller, and it sends the proactive message to the user.

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"] ?? string.Empty;
    }

    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. To test the bot locally with the Emulator, you can use the empty string ("").

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.

About the proactive turn

The continue conversation method uses the conversation reference and a turn callback handler to:

  1. Create a turn in which the bot application can send the proactive message. The adapter creates an event activity for this turn, with its name set to "ContinueConversation".
  2. Send the turn through the adapter's middleware pipeline.
  3. Call the turn callback handler to perform custom logic.

In the proactive messages sample, the turn callback handler is defined in the notify controller and sends the message directly to the conversation, without sending the proactive activity through the bot's normal turn handler. The sample code also does not access or update the bot's state on the proactive turn.

Many bots are stateful and use state to manage a conversation over multiple turns. When the continue conversation method creates a turn context, the turn will have the correct user and conversation state associated with it, and you can integrate proactive turns into your bot's logic. If you need the bot logic to be aware of the proactive message, you have a few options for doing so. You can:

  • Provide the bot's turn handler as the turn callback handler. The bot will then receive the "ContinueConversation" event activity.
  • Use the turn callback handler to add information to the turn context first, and then call the bot's turn handler.

In both of these cases, you will need to design your bot logic to handle the proactive event.

Next steps