Host Video Office Hours with SMS Notifications using Python and Twilio

April 07, 2022
Written by
Mia Adjei
Twilion
Reviewed by

Host Video Office Hours with SMS Notifications using Python and Twilio

This article is for reference only. We're not onboarding new customers to Programmable Video. Existing customers can continue to use the product until December 5, 2024.


We recommend migrating your application to the API provided by our preferred video partner, Zoom. We've prepared this migration guide to assist you in minimizing any service disruption.

When you think of "office hours", what comes to mind? Perhaps you imagine a professor who has set aside time for students to stop by their office to ask for advice or receive guidance. Or maybe you imagine a business leader who creates space on their calendar for colleagues and direct reports to ask questions or share their ideas.

With the rise of virtual and hybrid environments, many people have taken their office hours online, and video chat provides a great way to have face-to-face conversations without needing to be in the same physical location.

In this tutorial, you will learn how to build a virtual office hours application that allows you to create a new, named video room with a customized timeout window and receive an SMS notification when a participant joins the room. For this project, you will use Python and Flask, along with a bit of HTML and CSS. We'll use Twilio Video's Rooms API to create the video rooms, and Twilio SMS to send a text message to your mobile device when someone joins the room.

Let's get started!

Prerequisites

  • A free Twilio account. (If you register here, you'll receive $10 in Twilio credit when you upgrade to a paid account!)
  • Python 3.6 or newer
  • ngrok installed and set up on your machine
  • A phone that can receive SMS
  • A pre-built Video application that allows you to enter a participant's name and a room name. Bring/build your own, or you can use the open-source Twilio Video React app found here.

Create a new Flask project and install dependencies

To get started, open a new terminal window and navigate to where you would like to set up your project. Once you have done this, run the following commands to set up a new directory called office-hours and change into the directory:

mkdir office-hours
cd office-hours

Inside the office-hours directory, create two new subdirectories called static and templates, where your front-end files will live:

mkdir static
mkdir templates

Next, you'll need to create a Python virtual environment where you can install the dependencies for this project. If you are working in a Mac or Unix environment, run the following commands:

python3 -m venv venv
source venv/bin/activate

If you are working in a Windows environment, run the commands below instead:

python -m venv venv
venv\Scripts\activate

For this project, you will be using the following Python packages:

Run the following command in your terminal to install these packages in your virtual environment:

pip install flask python-dotenv twilio tinydb

Save your Twilio credentials as environment variables

Next, you will need to gather your Twilio credentials and set them so they can be used in your application.

Create a new file named .env at the root of your project and open it in your code editor. This file is where you will keep your configuration variables, such as your Twilio account credentials, which will be imported into the Flask application as environment variables. Open the .env file in your text editor and add the following variables:

TWILIO_ACCOUNT_SID=XXXXXXXXXXXXXXX
TWILIO_API_KEY_SID=XXXXXXXXXXXXXXX
TWILIO_API_KEY_SECRET=XXXXXXXXXXXXXXX
TWILIO_PHONE_NUMBER=XXXXXXXXXXXXXXX

You’ll need to replace the placeholder text above with your actual Twilio credentials, which can be found in the Twilio Console. Log in to the Twilio Console and find your Account SID:

Account info in Twilio Console

Copy and paste the value for Account SID to replace the placeholder text for TWILIO_ACCOUNT_SID.

Then, navigate to the API Keys section of the console and generate a new API Key. Copy the API Key's values for SID and Secret to replace the placeholder text for TWILIO_API_KEY_SID and TWILIO_API_KEY_SECRET.

Next, if you do not already have a Twilio phone number with SMS capabilities, you'll need to buy one. Learn how to buy a Twilio phone number here. If you already have a Twilio phone number, it will show in the account info section, as shown in the screenshot above. Copy this value and use it to replace the placeholder text for TWILIO_PHONE_NUMBER.

It’s very important to keep these private credentials secure and out of version control, so if you are using Git, create a .gitignore file at the root of your project. Here you can list the files and directories that you want git to ignore from being tracked or committed. Open .gitignore in your code editor and add the .env file, as shown below:

.env

Now you're ready to start building your Flask application.

Build the Flask server

Create and open a new file called app.py. This is the file that will contain all of your Python code. To set up the project, you'll need to create a new Flask application instance, a Twilio client instance, and a new TinyDB database.

Since this is a small tutorial project, we're just going to store our data in a TinyDB JSON file. However if you decide to develop this project further, you'll likely want to choose a different database for the version you deploy to production.

Inside app.py, paste the following code:

import os
from dotenv import load_dotenv
from flask import Flask, render_template, request, abort
from twilio.rest import Client
from tinydb import TinyDB, Query

load_dotenv()
TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID')
TWILIO_API_KEY_SID = os.environ.get('TWILIO_API_KEY_SID')
TWILIO_API_KEY_SECRET = os.environ.get('TWILIO_API_KEY_SECRET')
TWILIO_PHONE_NUMBER = os.environ.get('TWILIO_PHONE_NUMBER')

client = Client(TWILIO_API_KEY_SID, TWILIO_API_KEY_SECRET, TWILIO_ACCOUNT_SID)
db = TinyDB('office_hours.json')
app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

With this code, you have created a new Flask application with the necessary imports, the environment variables you set up in the previous step, a new instance of the Twilio client, and a new database. You also now have one route (using the app.route() decorator) that will render a file named index.html, which will contain the HTML for the application. Since you do not yet have that file, create the new index.html file inside the templates directory now. Open the file and paste in the following HTML code:

<!DOCTYPE html>
<html>
  <head>
    <title>Office Hours</title>
    <link rel='stylesheet' type='text/css' href="{{ url_for('static', filename='styles.css') }}">
  </head>
  <body>
    <div class='container'>
      <h1>Office Hours</h1>
      <form method='post' action="{{ url_for('get_or_create_room') }}" id='setup-form' name='setup-form'>
        <div class='data-field'>
          <label for='identity'>Name</label>
          <input type='text' name='identity' id='identity' required>
        </div>
        <div class='data-field'>
          <label for='phone'>Phone</label>
          <input type='text' name='phone' id='phone' required>
        </div>
        <div class='data-field'>
          <label for='room_name'>Room Name</label>
          <input type='text' name='room_name' id='room_name' required>
        </div>
        <button type='submit' id='button-create'>Create Office Hours Chat</button>
      </form>
      <section id='status'>
        <div id='status-message'>
          {% if status and status.video_room %}
              Success! Office hours room: {{status.video_room.name}} ✅
          {% endif %}
        </div>
      </section>
    </div>
  </body>
</html>

The UI for this application is not very complex. It includes a form that allows the user to enter their name, phone number, and the name of the office hours video room they want to receive people in. It also has a section to display a status message to let the user know whether the room was created successfully. In this HTML file, you also reference a styles.css file — let's create it now.

Create static/styles.css and paste in the following CSS styles:

.container {
  margin-top: 20px;
  font-family: sans-serif;
}

h1 {
  text-align: center;
}

form {
  text-align: center;
}

.data-field {
  margin-bottom: 0.5em;
}

input {
  padding: 0.6em;
  border: 2px solid rgb(177, 189, 233);
  border-radius: 5px;
  vertical-align: middle;
  font-size: 14px;
}

input:focus {
  outline: 0;
  border-color: #0e364d;
}

label {
  text-align: right;
  display: inline-block;
  width: 6em;
  margin: 0 1em 0 0;
}

button {
  padding: 0.75em 1em;
  border: none;
  background-color: rgb(27, 111, 189);
  border-radius: 4px;
  font-size: 100%;
  color: rgb(255, 255, 255);
  margin: 0.5em auto;
}

button:focus,
button:hover {
  background-color: rgb(88, 166, 240);
}

button:active {
  background-color: rgb(177, 189, 233);
}

#status {
  margin: 0.5em auto;
  text-align: center;
  color: rgb(13, 73, 32);
}

Now your app will look a bit more interesting.

It's time to run your server. Before we do that, however, create a new file at the root of your project called .flaskenv. This is where you'll add environment variables for your Flask configuration. Open .flaskenv and add the following lines:

FLASK_APP=app.py
FLASK_ENV=development

Then, in your terminal, start the server by running the following command:

flask run

Once the server is started, you will see logs like the following:

(venv) [office-hours] flask run 
 * Serving Flask app 'app.py' (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 290-231-040

If you open your browser to http://localhost:5000/, you will be able to see the application there:

Office hours application UI, with form fields and button

Now that you have the front end code set up, it's time to write the Python functions that will actually control the creation of the video room and the sending of the SMS when someone joins the room.

Create a video room with a customized timeout

Let's define a function called get_or_create_room() that will call the Twilio Video Rooms API to get or create the video room. Paste the following code into app.py to create the function:

@app.route('/', methods=['POST'])
def get_or_create_room():
    identity = request.form.get('identity').strip() or None
    phone = request.form.get('phone').strip() or None
    room_name = request.form.get('room_name').strip() or None

    if identity is None or phone is None or room_name is None:
        abort(400, 'Missing one of: identity, phone, room_name')

    # Try to get the room if it exists
    video_room_list = client.video.rooms.list(limit=20)
    found_video_rooms = [room for room in video_room_list if room.unique_name == room_name]
    video_room = found_video_rooms[0] if found_video_rooms else None

    request_url = request.url_root
    callback_url = f'{request_url}message'

    if video_room and video_room.status_callback != callback_url:
        abort(400, 'Status callback URL has changed; please create a room with a different name')

    # If the room does not exist, create a new one
    if not video_room:
        video_room = client.video.rooms.create(
            unique_name=room_name,
            empty_room_timeout=60,
            unused_room_timeout=60,
            status_callback=callback_url,
            status_callback_method='POST',
        )

    office_hours_appointment = {
        'identity': identity,
        'phone': phone,
        'room_name': video_room.unique_name,
        'room_sid': video_room.sid
    }

    # Check whether a record for this office hours meeting already exists
    office_hours_meeting = Query()
    selected_meeting = db.get(office_hours_meeting.room_sid == video_room.sid)

    # If a record does not exist, insert a new one
    if not selected_meeting:
        db.insert(office_hours_appointment)

    return render_template('index.html', status={
        'video_room': {
            'sid': video_room.sid,
            'name': video_room.unique_name,
            'empty_room_timeout': video_room.empty_room_timeout,
            'unused_room_timeout': video_room.unused_room_timeout,
            'status_callback': video_room.status_callback
        }
    })

This function takes the data sent by the client side of the application and verifies that everything needed is present. It then takes the variable for the video room name and checks whether an active room with the same name already exists. If it does not exist, then a new room will be created with the entered room name.

A key part of this function is setting the unused_room_timeout and the empty_room_timeout. By default, a new, empty video room created via the Rooms API will stay active for 5 minutes to allow people to join. If no one joins the room, it will close and will need to be created again. You can customize this value by setting the unused_room_timeout. This value represents the number of minutes the room will remain active if no one joins. In the case of the office hours room, we've set this value to 60, to allow the room to stay open for one hour even if no one shows up. Feel free to set this value to something else as per your preference.

The other value, empty_room_timeout, is for setting how long the room should remain open after the last participant leaves. In the case of an office hours room, the person in charge of the room may want to join the room when another participant joins, leave the room after their conversation ends, and then enter the room again if another participant joins. For this project, we have set the empty_room_timeout to 60 minutes as well, but feel free to customize this to your preference.

Another important part of this function is where we set a status_callback. This is the URL where your application will receive status information about events that occur inside the video room. A full list of the Rooms Status Callback events can be found here. For this project, we're only going to track the participant-connected event, because when someone joins the video room, we want to notify the room creator so they can join as well. The application will get the page's address from the request's url_root and add the /message route, which you will create in the next step.

If you want to know more about the resource properties of a video room, take a look at the documentation for the Rooms REST API here.

Send an SMS when someone joins the video room

It's time to create the route that will send an SMS to the video room's creator when someone joins the room. In app.py, below get_or_create_room(), paste the following /message route and send_participant_notification() function:

@app.route('/message', methods=['POST'])
def send_participant_notification():
    event = request.values.get('StatusCallbackEvent')

    if event == 'participant-connected':
        room_sid = request.values.get('RoomSid')

        office_hours_meeting = Query()

        # Query for the video meeting by its sid
        selected_meeting = db.get(office_hours_meeting.room_sid == room_sid)

        participant = request.values.get('ParticipantIdentity')
        room_name = selected_meeting.get('room_name')
        phone = selected_meeting.get('phone')
        identity = selected_meeting.get('identity')

        # Send an SMS to the creator of the video meeting
        client.messages.create(
            body=f'Hello {identity}! {participant} has joined your office hours room: {room_name}',
            from_=TWILIO_PHONE_NUMBER,
            to=phone
        )

    return ('', 204)

In this function, you get the status callback event and check whether it is a participant-connected event. If it is, the function will take the room_sid from the request and look up that room in your database to get the details for the person who should be notified. Once it finds that person's contact information, it uses their identity and phone number to send an SMS with details about the participant who has joined the video room.

For security reasons, you may want to verify that the HTTP requests to your application are truly coming from Twilio and not from a third party.

Twilio adds an X-Twilio-Signature header to its requests and also provides, in its helper libraries, a built-in request validator that you can use to verify the authenticity of this signature. Learn how to use the request validator in the documentation here.

Test your application

It's time to test out your application. For this section, you will need a Twilio Video application that allows you to enter a room name. Feel free to use one of your own applications if you have one, or you can use the open-source Twilio Video React application, as will be shown in this tutorial.

To get the code for the Twilio Video React application, clone the repository here: https://github.com/twilio/twilio-video-app-react. Follow the instructions in the README to set up the application locally or deploy it on Twilio Functions.

You will also need to allow your Flask application to be accessible to the wider internet for this test to work. For this part of the project, you can use ngrok to create a temporary public URL. To start a new ngrok tunnel, open up a new terminal window and run the following command from the root of your project:

ngrok http 5000

Once ngrok is running, you will see logs like the below in your terminal window:

ngrok by @inconshreveable                                       (Ctrl+C to quit)

Session Status                online
Account                      <YOUR_ACCOUNT_NAME>
Version                       2.3.40
Region                        <YOUR_REGION>
Web Interface                 http://127.0.0.1:4040
Forwarding                    <URL> -> http://localhost:5000
Forwarding                    <URL> -> http://localhost:5000

Connections                   ttl     opn     rt1     rt5     p50     p90
                             0       0       0.00    0.00    0.00    0.00

Take a look at the URLs next to Forwarding. Now, any requests that are made to these ngrok URLs will be forwarded to your Flask server.

Select the https URL and open it in your browser. You will see the office hours application there:

Office hours application

Enter your name, phone number in E.164 format, and a name for your office hours room in the form:

Office hours form filled out

Click the Create Office Hours Chat button. You should see a message pop up in the status message section that tells you the room has been created:

Form filled out, but with a success message visible below the submit button

Now, open your Video application in a new browser tab. Enter a participant name different from your name and then enter the name of the video room you just created in your Flask app. Join the video call as that person.

Video chat join screen, with space to enter your name and room name

After you enter the video room as a participant, check your mobile device, and you should see a new text message with details about the participant who joined the room:

Screenshot of SMS message from mobile device with details about video room participant

You can also verify that your message has been sent by checking the Programmable Messaging logs in the Twilio Console.

What's next for virtual office hours?

If you want to see this application in its entirety, you can view the repository on GitHub here.

You've now got a virtual office hours app that you can continue to develop for your use case. You've learned how to customize the timeouts for video rooms, and can set them to your liking. This is a very useful feature for when you want to make sure a video room will stay open for a specific time duration. It also allows the room's creator to not have to be present on the call the entire time, but to instead join when they get a notification that there is someone there to talk with.

Or perhaps you want to explore Status Callbacks even more, or send text messages to your app's users about other events occurring in their video rooms. Maybe they would like to know when a participant leaves, how many people are currently in the room, or other interesting data.

I hope you have some fun with this, and I can't wait to see what you build!

Mia Adjei is a Software Developer on the Developer Voices team. They love to help developers build out new project ideas and discover aha moments. Mia can be reached at madjei [at] twilio.com.