How to Keep Friends Accountable Using Twilio Programmable SMS, Python, and Flask

June 26, 2020
Written by
Diane Phan
Twilion
Reviewed by
Paul Kamp
Twilion

How to Keep Friends Accountable Using Twilio Programmable SMS and Video

It’s no mystery that the transition to online learning or working from home has impacted all of our lives. If you’re like me, you miss going out to a local coffee shop and being productive without distractions. It’s also nice to study and work with others around you to motivate you and keep you from checking social media every minute.

Completing this tutorial will allow you to host a system for you and your friends to be able to change your status throughout the day, everyday. Plus, you’ll realize just how easy it is to get started with coding!

In this post, I’ll show you how to develop a functional Python program to organize your work groups using Twilio Programmable SMS and Flask.  

Tutorial Requirements 

In order to build this project, you will need to have the following items ready:

  • Python 3.6 or newer. If your operating system does not provide a Python interpreter, you can go to python.org to download an installer.
  • ngrok. We will use this handy utility to connect the development version of our Python application running on your system to a public URL that Twilio can connect to. This is necessary for the development version of the application because your computer is likely behind a router or firewall, so it isn’t directly reachable on the Internet. If you don’t have ngrok installed, you can download a copy for Windows, MacOS or Linux.
  • A free or paid Twilio account. If you are new to Twilio get your free account now! (If you sign up through this link will give you $10 credit when you upgrade.)

Configuration

We’ll start off by creating a directory to store our project files. Inside your favorite terminal, enter:

$ mkdir accountability
$ cd accountability

Since we will be installing some Python packages for this specific project, we need to create a virtual environment.

If you are using a Unix or MacOS system, open a terminal and enter the following commands:

$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install flask python-dotenv twilio

NOTE: Depending on what version of Python you are on, you might have to specify python3.

If you are on a Windows machine, enter the following commands in a prompt window:

$ python -m venv venv
$ source venv\bin\activate
(venv) $ pip install flask python-dotenv twilio

If you are curious to learn more about the packages, you can check them out here:

  • The Flask framework, to create the web application that will receive message notifications from Twilio
  • The python-dotenv package, to load the environment variables created
  • The python-twilio package, to send messages through the Twilio service

Configure Twilio SMS

This is where we make sure you have a Twilio phone number activated. Log onto the Twilio Dashboard to view your Active Numbers. Click on the number you want to use to view the details. If you haven’t purchased a number yet, learn how to search for and buy a Twilio phone number.

Authenticate against Twilio Service

Next, we need to create a .env file to safely store some important credentials that will be used to authenticate against the Twilio service. We’re going to set this up now but we’ll discuss more about it in the section “Keep each other accountable”.

Here is what the .env file should look like:  

TWILIO_ACCOUNT_SID="<your account SID>"
TWILIO_AUTH_TOKEN="<your auth token>"
TWILIO_NUMBER= #use your Twilio Number here

For the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN variables, you can obtain the values that apply to your Twilio account from the Twilio Console:

Twilio Console screenshot blurred out account SID and authentication credentials

Use the following functions to import your .env variables. You can add this code under the lines that import all of your packages:

load_dotenv()
app = Flask(__name__)
TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID')
TWILIO_AUTH_TOKEN = os.environ.get('TWILIO_AUTH_TOKEN')
TWILIO_PHONE_NUMBER = os.environ.get('TWILIO_PHONE_NUMBER')
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

Receive and send messages with Twilio

The goal of our app is to allow friends to text your Twilio number and add themselves to the Work Accountability Directory. Your friends will be able to set their statuses by sending in a text message from their devices at any time in order to update the directory for other users to see.

Receive messages

The Twilio API for Programmable SMS uses a webhook (web callback) to allow real-time data to be delivered to other applications. This is especially important since we want to constantly be able to view our friends’ statuses.

Here’s some code to help you write your first webhook handler. Create a file named accountability.py and import the following packages and code:

import os
from flask import Flask, request, url_for
from twilio.rest import Client
from datetime import datetime, date, time, timedelta
from dotenv import load_dotenv

load_dotenv()
app = Flask(__name__)
load_dotenv()

app = Flask(__name__)
TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID')
TWILIO_AUTH_TOKEN = os.environ.get('TWILIO_AUTH_TOKEN')
TWILIO_PHONE_NUMBER = os.environ.get('TWILIO_PHONE_NUMBER')
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

studentsDictionary = {}

@app.route('/webhook', methods=['POST'])
def webhook():
    student = request.form.get('From')
    message = request.form.get('Body').lower()

    if message == 'check':
        if studentsDictionary:
            return respond(studentsDictionary)
        else:
            return respond(f'No one is here. Invite people to your Work Accountability Group')

We have a few things going on with the code above. The @app.route is a decorator from our Flask framework, which helps get API projects started quickly. This decorator will work with our webhook() function that will be included in a URL so that we can receive incoming messages from any user.

The student variable receives a value from Flask’s request.form function. When we look at the From parameter, Flask is able to parse the phone number of the sender in E.164 phone number format.

This variable is essential for our case because we will need to keep track of the users and numbers who are in the directory. As you might have guessed, the message variable is parsed from the Body parameter which allows our app to read what the user said and respond appropriately. In this example webhook, the user is asking to check the roster and status within the directory.

Lastly, since we have to keep track of our colleagues, represented as their phone number, as well as their statuses, a Python dictionary will do the work for us.

This data structure allows us to have one unique key, representing the person’s phone number. Each key will have it’s own status that can be updated. It is also a returnable object that conveniently lists out all the keys (students) and their values (statuses).

Reply to a message

Next, we will be discussing the respond() function that is called inside of the else statement. This function is called throughout the project to confirm with the user that we received their text message.

The webhook allows us to issue a response to the student, the one who sent the message, by providing a TwiML payload in response to the webhook. TwiML is short for Twilio Markup Language, an XML-based syntax that the webhook can use to tell Twilio how to handle the message.

The Twilio Helper Library for Python allows us to generate TwiML responses using classes and objects. Let’s check out how we can write our respond() function:

from twilio.twiml.messaging_response import MessagingResponse

def respond(message):
    response = MessagingResponse()
    response.message(message)
    return str(response)

Add the new import line to the top of the accountability.py file and the new respond() function above or below the webhook.

If you are curious to see what TwiML looks like, start a Python shell and enter the following code to generate an example response:

>>> from accountability import respond
>>> respond('this is my response')
'<?xml version="1.0" encoding="UTF-8"?><Response><Message>this is my response</Message></Response>'

Set up testing environment

Awesome job writing your first webhook! 

Now it’s time to test it out and make sure it’s running properly by sending and receiving a few text messages. Add a .flaskenv file (make sure you have the leading dot) to your project with the following lines:

FLASK_APP=accountability.py
FLASK_ENV=development

These incredibly helpful lines will save you time when it comes to testing and debugging your project.

  • FLASK_APP tells the Flask framework where our application is located
  • FLASK_ENV configures Flask to run in debug mode

These lines are convenient because every time you save the source file, the server will reload and reflect the changes. Neat huh?

So, we’ll move forward by starting our Flask application with the following command:

(venv) $ flask run

Image depicts the console output for the "flask run" command for the Work Accountability app

The screenshot above displays what your console will look like after running the command flask run. The service is running privately on your computer’s port 5000 and will wait for incoming connections. You will also notice that debugging mode is active, and that my flask server restarted to reflect my changes.

While flask is running in one terminal window, open up a second tab or terminal window. Start ngrok with the following command:

$ ngrok http 5000

Great, you have now enabled your Flask service publicly over the Internet!

Ngrok is a great tool because it allows you to create a temporary public domain that redirects HTTP requests to our local port 5000.

Note that if you are unable to install ngrok system-wide, try to locate the executable ngrok file in order to call it in your terminal using its full path.

Example ngrok run tunneling your app on port 5000

Your ngrok terminal will now look like the picture above. As you can see, there are URLs in the “Forwarding” section. These are public URLs that ngrok uses to redirect requests into our flask server.

Set up a webhook with Twilio

Copy the URL starting with https:// and return to the Twilio Console, where we tell Twilio to send incoming message notifications to this URL.

In the Twilio Console, click on the (...) on the left sidebar to scroll down and find Phone Numbers. Click on the active phone number that you want to use for this project and scroll down to “Messaging”.

Paste the URL copied from the ngrok session into the “A MESSAGE COMES IN” field and append /webhook, since that is our endpoint. Here is my example for reference:

Messaging callback or webhook inside the Twilio console for the Work Accountability App

The URL from ngrok is https://ad7e4814affe.ngrok.io/webhook 

Before you click on the “Save” button, make sure that the request method is set to HTTP POST.

Time to test things out! Grab your SMS-enabled phone and send a text message to your active Twilio number.

Example text message sent to the Work Accountability App responding to the user&#x27;s message "check"

Welp, guess that means we need to build out the directory now. At least we know the text messages are working!

Build your Work Accountability Group Organizing app

We’ve made it this far and we have seen how to receive messages from our Twilio number and respond to them. However, we need to spend time creating and personalizing our directory system.

Add yourself to the directory

To add yourself to the directory, we need to write some conditions in our webhook and reply with an appropriate message. If you’re already registered, the program should welcome you back.

Here’s the code that accomplishes that for you, along with an example of what your texts should look like at this point:

@app.route('/webhook', methods=['POST'])
def webhook():
    student = request.form.get('From')
    message = request.form.get('Body').lower()
    if message == 'check':
        if studentsDictionary:
            return respond(studentsDictionary)
        else:
            return respond(f'No one is here. Invite people to your Work Accountability Group')
    if message == 'hello' and student not in studentsDictionary:
        status = 'Online'
        studentsDictionary[student] = status
        return respond(f'Hello, {student}, you have been added to the directory')
    if message == 'hello' and student in studentsDictionary:
        status = 'Online'
        studentsDictionary[student] = status
        return respond(f'Welcome back, {student}!')

Now you can text your Twilio number with the message “hello” to register yourself.

Example response of user being added to the directory in the Work Accountability App

You can add these lines of code below the other code you have inside your webhook already.

Set up and change the student status

As a developer, it is more efficient to create functions when you know you’ll use them in multiple places.

We can create a set_status function to avoid writing the same lines of code multiple times. Instead, you can simply call a function passing in a status and the student variable:

python
def set_status(status, student):
    studentsDictionary[student] = status
    return respond(f'Your status has been changed to: ' + status)

For my statuses, I decided to use a numbering system to make it easier for the users to interact with the directory. I also implemented a condition to ensure that I exist in the directory before changing the status. If I’m in the directory, the program checks my message and proceeds to change my status.

Here are some example options included within the webhook’s if statements:

python
    if student in studentsDictionary and message == '1':
        status = 'Studying'
        return set_status(status, student)
    if student in studentsDictionary and message == '2':
        status = 'Taking a break'
        return set_status(status, student)

You can insert these if statements under the if message == 'check': line. Repeat the process of writing if statements, status codes, and messages until you are satisfied with your system.

Keep each other accountable

So far, one way to check if your colleagues are being productive is to text check to the Twilio number. However, it’s not healthy to work all day without breaks. In fact, it’s essential to take your mind off work for a while, even if it’s only for 10 minutes.

We’re going to use a new function called ‘render_template()’ which I’ll explain further in the next section. For now, add this to the top of your accountability.py file with the other packages you imported.

python 
from flask import Flask, render_template

Earlier, we set up a .env file so that we can authenticate against Twilio’s services. This allows us to access the Twilio CLI’s built in functions to retrieve our history of recipient_messages.

In the code I’m about to show you, I created a new array named recipient_studied_list to put the data of the user and their timestamp into string format so that it’s easier to read. Notice that I also changed my timezone to PST by adding timedelta(hours = -7) to all the timestamps, which you can adjust for yourself as well.  

python
@app.route('/studycheck', methods=['GET'])
def getStudyStatus(): 
    #check: how many people have been studying? 
    recipient_messages = client.messages.list(
                            date_sent = date.today(),
                            to=TWILIO_PHONE_NUMBER,
                            # limit=100
                        )
    recipient_studied_list = []
    for msg in recipient_messages:
        # get entire history of those who said they were studying 
        if msg.body is '1':
            updated_time = msg.date_updated + timedelta(hours = -7)
            updated_time = str(updated_time)
            recipient_studied_list.append(msg.from_ + ': started studying at ' + updated_time)
    return render_template('index.html', people=recipient_studied_list, action = 'studied')

As you can see, I created a route for the flask app to make an HTTP GET request where users in the directory can go to our unique ngrok URL with the appended parameter /studycheck appended at the end. For example, my link would be “https://ad7e4814affe.ngrok.io/studycheck”. You can share this URL amongst the people in your directory so they can all check on one another and view the history of what time people studied throughout the day.

In the next section, we’ll design a simple HTML page that can display the information clearly.

Style a simple HTML page

Now, I’ll show you some code to return HTML from Flask to display the history of status updates in a web browser.

In your accountability directory, create a folder named templates. Inside of that folder, create a file named index.html and copy this code inside.

<!DOCTYPE html>
<html>
    <head>
            <title>
                People who {{action}} today:
            </title>
    </head>
<body>
    <p>  
        <h1>People who {{action}} today:</h1> 
        {% for person in people %}
            {{person}} <br/>
        {% endfor %}
    </p>
</body>
</html>

Your Flask app can now use the render_template function that we imported earlier to display the index.html page and pass in any variables you want! Whoever has the URL can access this simple page on their web browser to view the history of responses.

Here’s a screenshot of the web browser view:

Example web browser view listing phone numbers of people and the time they started studying

On the flip side, you can copy the code but alter it to check if someone is taking too many breaks. By having information from both those who studied and those who took a break, you and your colleagues can check just how often people report themselves to take a break.

If you see an odd trend for someone, maybe you can reach out to them and see if they need someone to talk to!

(Optional) Message restrictions

An optional addition to your code is to handle the scenario where one of your friends accidentally sends an incorrect message that doesn’t do anything. To lead them on the correct path, it might be helpful to create a help_text() function that returns some text explaining how to use the system.

Here’s an example of what a help message could look like:

Example help text for the Work Accountability App

Run the Work Accountability Program

It’s time to wrap things up and make sure your program is working the way you want it to. If you want an example, view my code on GitHub.

Make sure you have one tab running flask and one tab running ngrok. If you closed it for any reason, start it again now with the following commands in their respective tabs. 

bash
(venv) $ flask run

And in the second tab:

bash
$ ngrok http 5000

Furthermore, make sure that your SMS endpoint is configured with the forwarding URL reported by ngrok. Each time you restart ngrok, the URL changes, so you will have to replace the URL in your Twilio Active Phone Numbers console. Remember to add the /webhook at the end of the ngrok forward URL.

It’s time to form your accountability directory with colleagues who want to help each other stay productive and get that bread! Be sure to tell your friends your Twilio Number so that they can text in, register themselves in the database, and update their status!

Conclusion: Building a Work Accountability app

You must be working productively if you made it this far, and now it’s time to help each other stay healthy while working! This simple SMS tutorial shows you how to create a database of students, coworkers, or colleagues who can update their statuses simultaneously throughout the day with the help of Python and Flask.

If people are using their phones to distract themselves, then this app is a great way to help people use their phones to be productive!

What’s next for Work Accountability apps?

Looking for ideas on how to expand? Try these:

Let me know if this app makes your productive work days more fun and healthy!

Diane Phan is a Developer Network Intern on the Developer Voices team. She loves to help beginner programmers get started on creative projects that involve fun pop culture references. She can be reached at dphan [at] twilio.com.