Send scheduled messages from a bot to a channel in Microsoft Teams

A common task in creating a bot for Microsoft Teams is sending messages from the bot to members of a channel on a specified schedule. While there are many great examples in python version of botbuilder respository such as Starting a new thread in a channel, some extra steps are necessary to adapt the samples to our use case. The previous example triggers the bot with on_message_activity, which is not what we are looking for.

I will use Celery as a task scheduler for sending a message. I will also use botbuilder connector to be able to connect to Microsoft API for Teams outside of our bot controller. Here is the the list of libraries that we need:

botbuilder-core==4.14.6
celery==5.3.4

Another challenge is that while sending a message to channel using Microsoft Graph API with delegated permission is possible, currently the same task with application type permission is not supported. Therefore we borrow the following code from botframework-connector and modify it so that it is suitable for Microsoft Teams channels:

from botbuilder.schema import ConversationParameters, ChannelAccount
from botframework.connector import ConnectorClient
from botframework.connector.auth import MicrosoftAppCredentials
from celery import Celery


app = Celery('tasks', broker='<broker-address>')

APP_ID = '<your-app-id>'
APP_PASSWORD = '<your-app-password>'
SERVICE_URL = 'https://smba.trafficmanager.net/teams/'
CHANNEL_ID = 'msteams'
BOT_ID = '<bot-id>' # for MS Teams app, it's '28:APP_ID'
TEAMS_CHANNEL_ID = '<channel-id>'

credentials = MicrosoftAppCredentials(APP_ID, APP_PASSWORD)
connector = ConnectorClient(credentials, base_url=SERVICE_URL)

@app.task
def send_message_to_teams():
    message = MessageFactory.text("Hello World!")
    conversation = connector.conversations.create_conversation(
        ConversationParameters(
            bot=ChannelAccount(id=BOT_ID),
            channel_data={"channel": {"id": TEAMS_CHANNEL_ID}},
            activity=message,
        )
    )

There is another method to send a conversation to a channel called connector.conversations.send_to_conversation() which does not work currently with channels in Microsoft Teams, so instead we add the activity argument to connector.conversations.create_conversation() so that it sends the message at the same time that it creates the conversation.

We also need the unique identifier for the channel that we want to send the messages (TEAMS_CHANNEL_ID). An easy way to find this ID is to right click on the channel and select Get Link to channel on our Teams app, and get the URL-encoded version. Microsoft Graph API provides the list of teams and channel as well:

url = https://graph.microsoft.com/v1
GET /teams
GET /teams/{team-id}/channels

I will configure the schedule in Celery beat so that it send the message every Monday morning at 7:30 a.m.:

from celery.schedules import crontab


app.conf.beat_schedule = {
    'send-message-to-teams': {
        'task': 'tasks.send_message_to_teams',
        'schedule': crontab(hour=7, minute=30, day_of_week=1),
    },
}

Finally, we need to run a Celery beat application to send the task to the worker:

$ celery -A tasks beat --loglevel=INFO

and a Celery worker that simply executes the task on our desired time:

$ celery -A tasks worker --loglevel=INFO

If we run the task manually from our application, we get the message from our bot in the channel!