ETA Notifications with Python and Flask

Download the Code

ETA Notifications

There are now many successful businesses that deliver goods or services to their customers on-demand.

Companies like Uber, TaskRabbit, and Instacart have built an entire industry around the fact that we, the customers, like to order things instantly, wherever we are. The key to those services working is notifying customers instantly when things change; customers need to know if there's a last second audible.

Uber relies on Twilio SMS to keep customers up to date on their ridesharing requests. Learn more here.

In this tutorial we'll build a notification system for our made-up on demand laundry service Laundr.io, and we'll show you how to implement ETA Notifications with Python and Flask.

Let's get started!  Click the arrow below to start the tutorial.

Trigger Notifications

Here we show a couple of routes which will be exercised by our delivery person.  There are two cases we'd like to handle:

  1. Delivery person picks up laundry to be delivered ( /pickup )
  2. Delivery person is arriving at the customer's house ( /deliver )

In a production app we would probably automatically trigger the second notification with GPS, but a button should show you the basics of how it's done.

Loading Code Samples...
Language
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
eta_notifications_flask/views.py
Send SMS Notification for delivery status

eta_notifications_flask/views.py

Now that you've seen the views, let's look at the Twilio REST Client, which we'll use to send the notifications themselves.

 

Set up the Twilio REST Client

We instantiate a Twilio REST client with our Twilio Account Credentials stored as environment variables.

You'll need a number you own through Twilio: TWILIO_NUMBER, as well as an Account SID and Auth Token: TWILIO_ACCOUNT_SID and AUTH_TOKEN.  These can be found in the Twilio Console.

console credentials

Loading Code Samples...
Language
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
eta_notifications_flask/views.py
Setting up the Twilio REST Client

eta_notifications_flask/views.py

Next up, let's look at what happens when a notification is triggered.

Handle a Notification Trigger

When a notification is required, we extract the phone number stored in each order and then simply send an SMS message with an appropriate body.  Easy!

Loading Code Samples...
Language
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
eta_notifications_flask/views.py
Handle a Notification trigger

eta_notifications_flask/views.py

Next, let's look closer at how we send the SMS.

Send the Message Out

This code shows how we actually send the SMS out to the customer.

Think it would be better with an image?  You're probably right - you can add an optional media_url to make it an MMS:

media_url='http://lorempixel.com/image_output/fashion-q-c-640-480-1.jpg'

In addition to the required parameters (and the optional media_url), we can pass a status_callback url to let us know if the message was delivered.

Loading Code Samples...
Language
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
eta_notifications_flask/views.py
Using Twilio Python client to send the SMS

eta_notifications_flask/views.py

The status update handler is interesting - let's zoom in on that next.

Handle a Twilio Status Callback

Twilio will make a post request each time our message status changes to one of the following: queued, failed, sent, delivered, or undelivered.

We then update this notification_status on the Order so that we can decide what to do next with the Order. This is a great place to add logic that would resend the message in the event of a failure, or send out an automated survey when the delivery message is successful.

Loading Code Samples...
Language
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
eta_notifications_flask/views.py
Use a Twilio Callback for status updates.

eta_notifications_flask/views.py

That's it! We've just implemented an on-demand notification service that alerts our customers when their order is picked up or arriving. You should now be able to add ETA Notifications to your own application very simply.

On the next pane we'll take a look at some other tutorials you might enjoy.

Where to Next?

Like Python?  Like Twilio?  Try these other tutorials:

Workflow Automation

Increase your rate of response by automating the workflows that are key to your business. In this tutorial, learn how to build a ready-for-scale automated SMS workflow for a vacation rental company.

Masked Numbers

Protect your users' privacy by anonymously connecting them with Twilio Voice and SMS. Learn how to create disposable phone numbers on-demand, so two users can communicate without exchanging personal information.

Did this help?

Thanks for checking this tutorial out! Let us know what you've built - or what you're building - on Twitter.

Mario Celi
Paul Kamp
Andrew Baker
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)
from eta_notifications_flask import app, db
from flask import url_for, flash, redirect, render_template, request
from twilio.rest import Client

from eta_notifications_flask.models import Order

def _send_sms_notification(to, message_body, callback_url):
    account_sid = app.config['TWILIO_ACCOUNT_SID']
    auth_token = app.config['TWILIO_AUTH_TOKEN']
    twilio_number = app.config['TWILIO_NUMBER']
    client = Client(account_sid, auth_token)
    client.messages.create(to=to,
                           from_=twilio_number,
                           body=message_body,
                           status_callback=callback_url)

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

@app.route('/orders')
def order_index():
    orders = Order.query.all()

    return render_template('index.html', orders=orders)

@app.route('/order/<order_id>')
def order_show(order_id):
    order = Order.query.get(order_id)

    return render_template('show.html', order=order)

@app.route('/order/<order_id>/pickup', methods=["POST"])
def order_pickup(order_id):
    order = Order.query.get(order_id)
    order.status = 'Shipped'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/pickup', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is done and on its way to you!',
                          callback_url)

    return redirect(url_for('order_show', order_id=order_id))

@app.route('/order/<order_id>/deliver', methods=["POST"])
def order_deliver(order_id):
    order = Order.query.get(order_id)
    order.status = 'Delivered'
    order.notification_status = 'queued'
    db.session.commit()

    callback_url = request.base_url.replace('/deliver', '') + '/notification/status/update'
    _send_sms_notification(order.customer_phone_number,
                          'Your laundry is arriving now.',
                          callback_url)

    return redirect(url_for('order_index'))

@app.route('/order/<order_id>/notification/status/update', methods=["POST"])
def order_deliver_status(order_id):
    order = Order.query.get(order_id)
    order.notification_status = request.form['MessageStatus']
    db.session.commit()

    return render_template('show.html', order=order)