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 to build out the web application that supports our user interface, and Celery 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 on GitHub to see how to run the code yourself.
Check out how Yelp uses SMS to confirm restaurant reservations for diners.
Let's get started! Click the button below to get started.
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 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.
.env.example
1# Environment variables for appointment-reminders-flask23# App settings4export DATABASE_URI=5export SECRET_KEY=asupersecr3tkeyshouldgo6export CELERY_BROKER_URL=redis://localhost:63797export CELERY_RESULT_BACKEND=redis://localhost:637989# Twilio settings10export TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXX11export TWILIO_AUTH_TOKEN=YYYYYYYYYYYYYYYYYY12export TWILIO_NUMBER=+###########
Now that the configuration is taken care of. We'll move on to the application structure.
The Application
object is the heart of any Flask app. Ours 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 as a broker for Celery. But you can also use any of the other available Celery brokers.
To get Celery to run locally on your machine, follow the instructions in the README.
application.py
1import flask2from flask_migrate import Migrate34from flask_sqlalchemy import SQLAlchemy56from celery import Celery78from config import config_classes9from views.appointment import (10AppointmentFormResource,11AppointmentResourceCreate,12AppointmentResourceDelete,13AppointmentResourceIndex,14)151617class Route(object):18def __init__(self, url, route_name, resource):19self.url = url20self.route_name = route_name21self.resource = resource222324handlers = [25Route('/', 'appointment.index', AppointmentResourceIndex),26Route('/appointment', 'appointment.create', AppointmentResourceCreate),27Route(28'/appointment/<int:id>/delete', 'appointment.delete', AppointmentResourceDelete29),30Route('/appointment/new', 'appointment.new', AppointmentFormResource),31]323334class Application(object):35def __init__(self, routes, environment):36self.flask_app = flask.Flask(__name__)37self.routes = routes38self._configure_app(environment)39self._set_routes()4041def celery(self):42celery = Celery(43self.flask_app.import_name, broker=self.flask_app.config['CELERY_BROKER_URL']44)45celery.conf.update(self.flask_app.config)4647TaskBase = celery.Task4849class ContextTask(TaskBase):50abstract = True5152def __call__(self, *args, **kwargs):53with self.flask_app.app_context():54return TaskBase.__call__(self, *args, **kwargs)5556celery.Task = ContextTask5758return celery5960def _set_routes(self):61for route in self.routes:62app_view = route.resource.as_view(route.route_name)63self.flask_app.add_url_rule(route.url, view_func=app_view)6465def _configure_app(self, env):66self.flask_app.config.from_object(config_classes[env])67self.db = SQLAlchemy(self.flask_app)68self.migrate = Migrate()69self.migrate.init_app(self.flask_app, self.db)
With our Application
ready, let's create an Appointment model
.
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 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 library helps with this kind of time arithmetic.
models/appointment.py
1from database import db23import arrow456class Appointment(db.Model):7__tablename__ = 'appointments'89id = db.Column(db.Integer, primary_key=True)10name = db.Column(db.String(50), nullable=False)11phone_number = db.Column(db.String(50), nullable=False)12delta = db.Column(db.Integer, nullable=False)13time = db.Column(db.DateTime, nullable=False)14timezone = db.Column(db.String(50), nullable=False)1516def __repr__(self):17return '<Appointment %r>' % self.name1819def get_notification_time(self):20appointment_time = arrow.get(self.time)21reminder_time = appointment_time.shift(minutes=-self.delta)22return reminder_time
Next we will use this model to create a new Appointment
and schedule a reminder.
This view handles creating new appointments and scheduling new reminders. It accepts POST
data sent to the /appointment
URL.
We use WTForms to validate the form data using a class called NewAppointmentForm
that we defined in forms/new_appointment.py
.
After that we use arrow 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
1import arrow23from flask.views import MethodView4from flask import render_template, request, redirect, url_for56from database import db7from models.appointment import Appointment8from forms.new_appointment import NewAppointmentForm91011class AppointmentResourceDelete(MethodView):12def post(self, id):13appt = db.session.query(Appointment).filter_by(id=id).one()14db.session.delete(appt)15db.session.commit()1617return redirect(url_for('appointment.index'), code=303)181920class AppointmentResourceCreate(MethodView):21def post(self):22form = NewAppointmentForm(request.form)2324if form.validate():25from tasks import send_sms_reminder2627appt = Appointment(28name=form.data['name'],29phone_number=form.data['phone_number'],30delta=form.data['delta'],31time=form.data['time'],32timezone=form.data['timezone'],33)3435appt.time = arrow.get(appt.time, appt.timezone).to('utc').naive3637db.session.add(appt)38db.session.commit()39send_sms_reminder.apply_async(40args=[appt.id], eta=appt.get_notification_time()41)4243return redirect(url_for('appointment.index'), code=303)44else:45return render_template('appointments/new.html', form=form), 400464748class AppointmentResourceIndex(MethodView):49def get(self):50all_appointments = db.session.query(Appointment).all()51return render_template('appointments/index.html', appointments=all_appointments)525354class AppointmentFormResource(MethodView):55def get(self):56form = NewAppointmentForm()57return render_template('appointments/new.html', form=form)
We'll look at that task next.
Our tasks.py
module contains the definition for our send_sms_reminder
task. At the top of this module we use the twilio-python 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
1import arrow23from celery import Celery4from sqlalchemy.orm.exc import NoResultFound5from twilio.rest import Client67from reminders import db, app8from models.appointment import Appointment910twilio_account_sid = app.config['TWILIO_ACCOUNT_SID']11twilio_auth_token = app.config['TWILIO_AUTH_TOKEN']12twilio_number = app.config['TWILIO_NUMBER']13client = Client(twilio_account_sid, twilio_auth_token)1415celery = Celery(app.import_name)16celery.conf.update(app.config)171819class ContextTask(celery.Task):20def __call__(self, *args, **kwargs):21with app.app_context():22return self.run(*args, **kwargs)232425celery.Task = ContextTask262728@celery.task()29def send_sms_reminder(appointment_id):30try:31appointment = db.session.query(Appointment).filter_by(id=appointment_id).one()32except NoResultFound:33return3435time = arrow.get(appointment.time).to(appointment.timezone)36body = "Hello {0}. You have an appointment at {1}!".format(37appointment.name, time.format('h:mm a')38)39to = appointment.phone_number40client.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 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 call to client.messages.create()
. We use our customer's phone number as the to
argument and our Twilio number as the from_
argument.
tasks.py
1import arrow23from celery import Celery4from sqlalchemy.orm.exc import NoResultFound5from twilio.rest import Client67from reminders import db, app8from models.appointment import Appointment910twilio_account_sid = app.config['TWILIO_ACCOUNT_SID']11twilio_auth_token = app.config['TWILIO_AUTH_TOKEN']12twilio_number = app.config['TWILIO_NUMBER']13client = Client(twilio_account_sid, twilio_auth_token)1415celery = Celery(app.import_name)16celery.conf.update(app.config)171819class ContextTask(celery.Task):20def __call__(self, *args, **kwargs):21with app.app_context():22return self.run(*args, **kwargs)232425celery.Task = ContextTask262728@celery.task()29def send_sms_reminder(appointment_id):30try:31appointment = db.session.query(Appointment).filter_by(id=appointment_id).one()32except NoResultFound:33return3435time = arrow.get(appointment.time).to(appointment.timezone)36body = "Hello {0}. You have an appointment at {1}!".format(37appointment.name, time.format('h:mm a')38)39to = appointment.phone_number40client.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:
Put a button on your web page that connects visitors to live support or sales people via telephone.
Improve the security of your Flask app's login functionality by adding two-factor authentication via text message.