Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Send Appointment Reminders with Python and Flask


(information)

Info

Ahoy! We now recommend you build your appointment reminders with Twilio's built in Message Scheduling functionality. Head on over to the Message Scheduling documentation to learn more about scheduling messages!

This web application shows how you can use Twilio to send your customers a text message reminding them of upcoming appointments.

We use Flask(link takes you to an external page) to build out the web application that supports our user interface, and Celery(link takes you to an external page) to send the reminder text messages to our customers at the right time.

In this tutorial, we'll point out the key bits of code that make this application work. Check out the project README(link takes you to an external page) on GitHub to see how to run the code yourself.

Check out how Yelp uses SMS to confirm restaurant reservations for diners.(link takes you to an external page)

Let's get started! Click the button below to get started.


Configure the application to use Twilio

configure-the-application-to-use-twilio page anchor

Before we can use the Twilio API to send reminder text messages, we need to configure our account credentials. These can be found on your Twilio Console. You'll also need an SMS-enabled phone number - you can find or purchase a new one to use here.

We put these environment variables in a .env file and use autoenv(link takes you to an external page) to apply them every time we work on the project. More information on how to configure this application can be found in the project README(link takes you to an external page).

Configure the environment variables

configure-the-environment-variables page anchor

.env.example


_12
# Environment variables for appointment-reminders-flask
_12
_12
# App settings
_12
export DATABASE_URI=
_12
export SECRET_KEY=asupersecr3tkeyshouldgo
_12
export CELERY_BROKER_URL=redis://localhost:6379
_12
export CELERY_RESULT_BACKEND=redis://localhost:6379
_12
_12
# Twilio settings
_12
export TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXX
_12
export TWILIO_AUTH_TOKEN=YYYYYYYYYYYYYYYYYY
_12
export TWILIO_NUMBER=+###########

Now that the configuration is taken care of. We'll move on to the application structure.


The application structure

the-application-structure page anchor

The Application object is the heart of any Flask app. Our's initializes the app, sets the URLs, and pulls in all our environment variables.

The celery method is boilerplate to configure Celery using settings and context from our Flask application. Our app uses Redis(link takes you to an external page) as a broker for Celery. But you can also use any of the other available Celery brokers(link takes you to an external page).

To get Celery to run locally on your machine, follow the instructions in the README(link takes you to an external page).

Our core application code

our-core-application-code page anchor

application.py


_69
import flask
_69
from flask_migrate import Migrate
_69
_69
from flask_sqlalchemy import SQLAlchemy
_69
_69
from celery import Celery
_69
_69
from config import config_classes
_69
from views.appointment import (
_69
AppointmentFormResource,
_69
AppointmentResourceCreate,
_69
AppointmentResourceDelete,
_69
AppointmentResourceIndex,
_69
)
_69
_69
_69
class Route(object):
_69
def __init__(self, url, route_name, resource):
_69
self.url = url
_69
self.route_name = route_name
_69
self.resource = resource
_69
_69
_69
handlers = [
_69
Route('/', 'appointment.index', AppointmentResourceIndex),
_69
Route('/appointment', 'appointment.create', AppointmentResourceCreate),
_69
Route(
_69
'/appointment/<int:id>/delete', 'appointment.delete', AppointmentResourceDelete
_69
),
_69
Route('/appointment/new', 'appointment.new', AppointmentFormResource),
_69
]
_69
_69
_69
class Application(object):
_69
def __init__(self, routes, environment):
_69
self.flask_app = flask.Flask(__name__)
_69
self.routes = routes
_69
self._configure_app(environment)
_69
self._set_routes()
_69
_69
def celery(self):
_69
celery = Celery(
_69
self.flask_app.import_name, broker=self.flask_app.config['CELERY_BROKER_URL']
_69
)
_69
celery.conf.update(self.flask_app.config)
_69
_69
TaskBase = celery.Task
_69
_69
class ContextTask(TaskBase):
_69
abstract = True
_69
_69
def __call__(self, *args, **kwargs):
_69
with self.flask_app.app_context():
_69
return TaskBase.__call__(self, *args, **kwargs)
_69
_69
celery.Task = ContextTask
_69
_69
return celery
_69
_69
def _set_routes(self):
_69
for route in self.routes:
_69
app_view = route.resource.as_view(route.route_name)
_69
self.flask_app.add_url_rule(route.url, view_func=app_view)
_69
_69
def _configure_app(self, env):
_69
self.flask_app.config.from_object(config_classes[env])
_69
self.db = SQLAlchemy(self.flask_app)
_69
self.migrate = Migrate()
_69
self.migrate.init_app(self.flask_app, self.db)

With our Application ready, let's create an Appointment model.


Our Appointment model is pretty simple. The name and phone_number fields tell us who to send the reminder to. The time, timezone, and delta fields tell us when to send the reminder.

We use SQLAlchemy(link takes you to an external page) to power our model and give us a nice ORM interface to use it with.

We added an extra method, get_notification_time, to help us determine the right time to send our reminders. The handy arrow(link takes you to an external page) library makes this kind of time arithmatic easy.

models/appointment.py


_22
from database import db
_22
_22
import arrow
_22
_22
_22
class Appointment(db.Model):
_22
__tablename__ = 'appointments'
_22
_22
id = db.Column(db.Integer, primary_key=True)
_22
name = db.Column(db.String(50), nullable=False)
_22
phone_number = db.Column(db.String(50), nullable=False)
_22
delta = db.Column(db.Integer, nullable=False)
_22
time = db.Column(db.DateTime, nullable=False)
_22
timezone = db.Column(db.String(50), nullable=False)
_22
_22
def __repr__(self):
_22
return '<Appointment %r>' % self.name
_22
_22
def get_notification_time(self):
_22
appointment_time = arrow.get(self.time)
_22
reminder_time = appointment_time.shift(minutes=-self.delta)
_22
return reminder_time

Next we will use this model to create a new Appointment and schedule a reminder.


Scheduling new reminders

scheduling-new-reminders page anchor

This view handles creating new appointments and scheduling new reminders. It accepts POST data sent to the /appointment URL.

We use WTForms(link takes you to an external page) to validate the form data using a class called NewAppointmentForm that we defined in forms/new_appointment.py.

After that we use arrow(link takes you to an external page) to convert the time zone of the appointment's time to UTC time.

We then save our new Appointment object and schedule the reminder using a Celery task we defined called send_sms_reminder.

views/appointment.py


_57
import arrow
_57
_57
from flask.views import MethodView
_57
from flask import render_template, request, redirect, url_for
_57
_57
from database import db
_57
from models.appointment import Appointment
_57
from forms.new_appointment import NewAppointmentForm
_57
_57
_57
class AppointmentResourceDelete(MethodView):
_57
def post(self, id):
_57
appt = db.session.query(Appointment).filter_by(id=id).one()
_57
db.session.delete(appt)
_57
db.session.commit()
_57
_57
return redirect(url_for('appointment.index'), code=303)
_57
_57
_57
class AppointmentResourceCreate(MethodView):
_57
def post(self):
_57
form = NewAppointmentForm(request.form)
_57
_57
if form.validate():
_57
from tasks import send_sms_reminder
_57
_57
appt = Appointment(
_57
name=form.data['name'],
_57
phone_number=form.data['phone_number'],
_57
delta=form.data['delta'],
_57
time=form.data['time'],
_57
timezone=form.data['timezone'],
_57
)
_57
_57
appt.time = arrow.get(appt.time, appt.timezone).to('utc').naive
_57
_57
db.session.add(appt)
_57
db.session.commit()
_57
send_sms_reminder.apply_async(
_57
args=[appt.id], eta=appt.get_notification_time()
_57
)
_57
_57
return redirect(url_for('appointment.index'), code=303)
_57
else:
_57
return render_template('appointments/new.html', form=form), 400
_57
_57
_57
class AppointmentResourceIndex(MethodView):
_57
def get(self):
_57
all_appointments = db.session.query(Appointment).all()
_57
return render_template('appointments/index.html', appointments=all_appointments)
_57
_57
_57
class AppointmentFormResource(MethodView):
_57
def get(self):
_57
form = NewAppointmentForm()
_57
return render_template('appointments/new.html', form=form)

We'll look at that task next.


Set up a Twilio API client

set-up-a-twilio-api-client page anchor

Our tasks.py module contains the definition for our send_sms_reminder task. At the top of this module we use the twilio-python(link takes you to an external page) library to create a new instance of Client.

We'll use this client object to send a text message using the Twilio API in our send_sms_reminder function.

tasks.py


_40
import arrow
_40
_40
from celery import Celery
_40
from sqlalchemy.orm.exc import NoResultFound
_40
from twilio.rest import Client
_40
_40
from reminders import db, app
_40
from models.appointment import Appointment
_40
_40
twilio_account_sid = app.config['TWILIO_ACCOUNT_SID']
_40
twilio_auth_token = app.config['TWILIO_AUTH_TOKEN']
_40
twilio_number = app.config['TWILIO_NUMBER']
_40
client = Client(twilio_account_sid, twilio_auth_token)
_40
_40
celery = Celery(app.import_name)
_40
celery.conf.update(app.config)
_40
_40
_40
class ContextTask(celery.Task):
_40
def __call__(self, *args, **kwargs):
_40
with app.app_context():
_40
return self.run(*args, **kwargs)
_40
_40
_40
celery.Task = ContextTask
_40
_40
_40
@celery.task()
_40
def send_sms_reminder(appointment_id):
_40
try:
_40
appointment = db.session.query(Appointment).filter_by(id=appointment_id).one()
_40
except NoResultFound:
_40
return
_40
_40
time = arrow.get(appointment.time).to(appointment.timezone)
_40
body = "Hello {0}. You have an appointment at {1}!".format(
_40
appointment.name, time.format('h:mm a')
_40
)
_40
to = appointment.phone_number
_40
client.messages.create(to, from_=twilio_number, body=body)

Let's look at send_sms_reminder now.


This is the send_sms_reminder function we called in our appointment.create view. Our function starts with an appointment_id parameter, which we use to retrieve an Appointment object from the database - a Celery best practice.

To compose the body of our text message, we use arrow(link takes you to an external page) again to convert the UTC time stored in our appointment to the local time zone of our customer.

After that, sending the message itself is a simple call to client.messages.create(). We use our customer's phone number as the to argument and our Twilio number as the from_ argument.

Perform the actual task of sending a SMS

perform-the-actual-task-of-sending-a-sms page anchor

tasks.py


_40
import arrow
_40
_40
from celery import Celery
_40
from sqlalchemy.orm.exc import NoResultFound
_40
from twilio.rest import Client
_40
_40
from reminders import db, app
_40
from models.appointment import Appointment
_40
_40
twilio_account_sid = app.config['TWILIO_ACCOUNT_SID']
_40
twilio_auth_token = app.config['TWILIO_AUTH_TOKEN']
_40
twilio_number = app.config['TWILIO_NUMBER']
_40
client = Client(twilio_account_sid, twilio_auth_token)
_40
_40
celery = Celery(app.import_name)
_40
celery.conf.update(app.config)
_40
_40
_40
class ContextTask(celery.Task):
_40
def __call__(self, *args, **kwargs):
_40
with app.app_context():
_40
return self.run(*args, **kwargs)
_40
_40
_40
celery.Task = ContextTask
_40
_40
_40
@celery.task()
_40
def send_sms_reminder(appointment_id):
_40
try:
_40
appointment = db.session.query(Appointment).filter_by(id=appointment_id).one()
_40
except NoResultFound:
_40
return
_40
_40
time = arrow.get(appointment.time).to(appointment.timezone)
_40
body = "Hello {0}. You have an appointment at {1}!".format(
_40
appointment.name, time.format('h:mm a')
_40
)
_40
to = appointment.phone_number
_40
client.messages.create(to, from_=twilio_number, body=body)

That's it! Our Flask application is all set to send out reminders for upcoming appointments.


We hope you found this sample application useful.

If you're a Python developer working with Twilio and Flask, you might enjoy these other tutorials:

Click to Call

Put a button on your web page that connects visitors to live support or sales people via telephone.

Two-Factor Authentication

Improve the security of your Flask app's login functionality by adding two-factor authentication via text message.

Did this help?

did-this-help page anchor

Thanks for checking out this tutorial! If you have any feedback to share with us, please reach out on Twitter(link takes you to an external page)... we'd love to hear your thoughts, and know what you're building!


Rate this page: