SMS and MMS Marketing Notifications with Python and Flask

Download the Code

Ready to implement SMS and MMS marketing notifications and see what potentially very high engagement rates will do for you?  

Excellent!  Today we're going to take you through implementing marketing notifications in Python and the Flask Microframework.

Here's how it'll work at a high level:

  1. A potential customer sends an SMS to a Twilio phone number you advertise online, on TV, in print, on a billboard, or so on.
  2. Your application confirms that the user wants to receive SMS and MMS notifications from your application and is added to the subscriber list.
  3. An administrator or marketing campaign manager crafts a message that is sent to subscribers via SMS/MMS message.

Learn how Walmart sent daily deals to customers as part of their "Value of the day" marketing campaign.

Building Blocks

To get this done, you'll be working with the following tools:

  • TwiML and the <Message> verb: We'll use TwiML, the Twilio Markup Language, to manage interactions initiated by the user via SMS.
  • Messages Resource: We will use the REST API to broadcast messages to all subscribers.

Let's get started!

Click the button below to move on to begin the tutorial.

The Subscriber Model

In order to send out marketing notifications to a subscriber, we need to provide the right model.

  • phone_number stores where to send the notifications.
  • subscribed identifies which subscribers are active (only active subscribers will receive notifications).
Loading Code Samples...
Language
from marketing_notifications_python.models import app_db

db = app_db()


class Subscriber(db.Model):
    __tablename__ = "subscribers"

    id = db.Column(db.Integer, primary_key=True)
    phone_number = db.Column(db.String, nullable=False)
    subscribed = db.Column(db.Boolean, nullable=False, default=True)

    def __repr__(self):
        return '<Subscriber %r %r>' % self.phone_number, self.subscribed
marketing_notifications_python/models/subscriber.py
Subscriber Python class

marketing_notifications_python/models/subscriber.py

Next up: how to handle incoming messages.

Handling Incoming Messages

This is the endpoint that will be called every time our application receives a message.

  1. We check if a user is in our database.  If the user is not, encourage them to 'subscribe' and return TwiML through the helper library.
  2. If the user was registered, check that the command has a valid 'subscribe' or 'unsubscribe' in the body.
  3. If the command is valid, set the user's status to subscribed or not subscribed, respectively, and return appropriate TwiML through the helper library.

So now we've created a Subscriber model to keep track of subscribers, unsubscribers, and new customers.

Loading Code Samples...
Language
from flask import request, flash
from marketing_notifications_python.forms import SendMessageForm
from marketing_notifications_python.models import init_models_module
from marketing_notifications_python.twilio import init_twilio_module
from marketing_notifications_python.view_helpers import twiml, view
from flask import Blueprint
from marketing_notifications_python.twilio.twilio_services import TwilioServices


def construct_view_blueprint(app, db):
    SUBSCRIBE_COMMAND = "subscribe"
    UNSUBSCRIBE_COMMAND = "unsubscribe"

    views = Blueprint("views", __name__)

    init_twilio_module(app)
    init_models_module(db)
    from marketing_notifications_python.models.subscriber import Subscriber

    @views.route('/', methods=["GET", "POST"])
    @views.route('/notifications', methods=["GET", "POST"])
    def notifications():
        form = SendMessageForm()
        if request.method == 'POST' and form.validate_on_submit():
            subscribers = Subscriber.query.filter(Subscriber.subscribed).all()
            if len(subscribers) > 0:
                flash('Messages on their way!')
                twilio_services = TwilioServices()
                for s in subscribers:
                    twilio_services.send_message(s.phone_number, form.message.data, form.imageUrl.data)
            else:
                flash('No subscribers found!')

            form.reset()
            return view('notifications', form)

        return view('notifications', form)

    @views.route('/message', methods=["POST"])
    def message():
        subscriber = Subscriber.query.filter(Subscriber.phone_number == request.form['From']).first()
        if subscriber is None:
            subscriber = Subscriber(phone_number=request.form['From'])
            db.session.add(subscriber)
            db.session.commit()
            output = "Thanks for contacting TWBC! Text 'subscribe' if you would like to receive updates via text message."
        else:
            output = _process_message(request.form['Body'], subscriber)
            db.session.commit()

        twilio_services = TwilioServices()
        return twiml(twilio_services.respond_message(output))

    def _process_message(message, subscriber):
        output = "Sorry, we don't recognize that command. Available commands are: 'subscribe' or 'unsubscribe'."

        if message.startswith(SUBSCRIBE_COMMAND) or message.startswith(UNSUBSCRIBE_COMMAND):
            subscriber.subscribed = message.startswith(SUBSCRIBE_COMMAND)

            if subscriber.subscribed:
                output = "You are now subscribed for updates."
            else:
                output = "You have unsubscribed from notifications. Text 'subscribe' to start receiving updates again"

        return output

    return views
marketing_notifications_python/views.py
Register a new subscriber in the database or apply a user command

marketing_notifications_python/views.py

Next, let's take a look at how to reply to received messages.

Replying to Received Messages

We ask Twilio to reply with an SMS by creating a twiml.Response and calling the message method on the created object.

Loading Code Samples...
Language
from marketing_notifications_python.twilio import account_sid, auth_token, phone_number
from twilio.twiml.messaging_response import MessagingResponse
from twilio.rest import Client


class TwilioServices:
    twilio_client = None

    def __init__(self):
        if TwilioServices.twilio_client is None:
            self.twilio_client = Client(account_sid(), auth_token())

    def send_message(self, to, message, image_url):
        self.twilio_client.messages.create(
            to=to,
            from_=phone_number(),
            body=message,
            media_url=image_url
        )

    def respond_message(self, message):
        response = MessagingResponse()
        response.message(message)
        return response
marketing_notifications_python/twilio/twilio_services.py
Create a TwiML Response containing our message

marketing_notifications_python/twilio/twilio_services.py

Now let's look at how to manage subscriptions.

Managing Subscriptions

We want to provide the user with two SMS commands to manage their subscription status: subscribe and unsubscribe.

These commands will toggle a boolean for their Subscriber record in the database.  On the business side, this boolean will determine whether or not they receive messages from our marketing campaign. To respect our user's desires, don't opt them in automatically - rather, they have to confirm that they want to receive our messages.

To make this happen, we will need to update the controller logic which handles the incoming text message to do a couple things:

  • If it is a subscribe or unsubscribe command, then create/update their subscription with the right status in the database.
  • If it is a command we don't recognize, send them a message with the available commands.
Loading Code Samples...
Language
from flask import request, flash
from marketing_notifications_python.forms import SendMessageForm
from marketing_notifications_python.models import init_models_module
from marketing_notifications_python.twilio import init_twilio_module
from marketing_notifications_python.view_helpers import twiml, view
from flask import Blueprint
from marketing_notifications_python.twilio.twilio_services import TwilioServices


def construct_view_blueprint(app, db):
    SUBSCRIBE_COMMAND = "subscribe"
    UNSUBSCRIBE_COMMAND = "unsubscribe"

    views = Blueprint("views", __name__)

    init_twilio_module(app)
    init_models_module(db)
    from marketing_notifications_python.models.subscriber import Subscriber

    @views.route('/', methods=["GET", "POST"])
    @views.route('/notifications', methods=["GET", "POST"])
    def notifications():
        form = SendMessageForm()
        if request.method == 'POST' and form.validate_on_submit():
            subscribers = Subscriber.query.filter(Subscriber.subscribed).all()
            if len(subscribers) > 0:
                flash('Messages on their way!')
                twilio_services = TwilioServices()
                for s in subscribers:
                    twilio_services.send_message(s.phone_number, form.message.data, form.imageUrl.data)
            else:
                flash('No subscribers found!')

            form.reset()
            return view('notifications', form)

        return view('notifications', form)

    @views.route('/message', methods=["POST"])
    def message():
        subscriber = Subscriber.query.filter(Subscriber.phone_number == request.form['From']).first()
        if subscriber is None:
            subscriber = Subscriber(phone_number=request.form['From'])
            db.session.add(subscriber)
            db.session.commit()
            output = "Thanks for contacting TWBC! Text 'subscribe' if you would like to receive updates via text message."
        else:
            output = _process_message(request.form['Body'], subscriber)
            db.session.commit()

        twilio_services = TwilioServices()
        return twiml(twilio_services.respond_message(output))

    def _process_message(message, subscriber):
        output = "Sorry, we don't recognize that command. Available commands are: 'subscribe' or 'unsubscribe'."

        if message.startswith(SUBSCRIBE_COMMAND) or message.startswith(UNSUBSCRIBE_COMMAND):
            subscriber.subscribed = message.startswith(SUBSCRIBE_COMMAND)

            if subscriber.subscribed:
                output = "You are now subscribed for updates."
            else:
                output = "You have unsubscribed from notifications. Text 'subscribe' to start receiving updates again"

        return output

    return views
marketing_notifications_python/views.py
Validate and apply a subscriber's command

marketing_notifications_python/views.py

Next up, let's look at sending the marketing notifications.

Sending Marketing Notifications

On the server side, we retreive the message text and potential image URL.  Next we loop through all Subscribers and call the method send_message on our TwilioServices domain object to send messages.

When the messages are on their way, we redirect back to the marketing form with a custom flash message containing feedback about the messaging attempt.

Loading Code Samples...
Language
from flask import request, flash
from marketing_notifications_python.forms import SendMessageForm
from marketing_notifications_python.models import init_models_module
from marketing_notifications_python.twilio import init_twilio_module
from marketing_notifications_python.view_helpers import twiml, view
from flask import Blueprint
from marketing_notifications_python.twilio.twilio_services import TwilioServices


def construct_view_blueprint(app, db):
    SUBSCRIBE_COMMAND = "subscribe"
    UNSUBSCRIBE_COMMAND = "unsubscribe"

    views = Blueprint("views", __name__)

    init_twilio_module(app)
    init_models_module(db)
    from marketing_notifications_python.models.subscriber import Subscriber

    @views.route('/', methods=["GET", "POST"])
    @views.route('/notifications', methods=["GET", "POST"])
    def notifications():
        form = SendMessageForm()
        if request.method == 'POST' and form.validate_on_submit():
            subscribers = Subscriber.query.filter(Subscriber.subscribed).all()
            if len(subscribers) > 0:
                flash('Messages on their way!')
                twilio_services = TwilioServices()
                for s in subscribers:
                    twilio_services.send_message(s.phone_number, form.message.data, form.imageUrl.data)
            else:
                flash('No subscribers found!')

            form.reset()
            return view('notifications', form)

        return view('notifications', form)

    @views.route('/message', methods=["POST"])
    def message():
        subscriber = Subscriber.query.filter(Subscriber.phone_number == request.form['From']).first()
        if subscriber is None:
            subscriber = Subscriber(phone_number=request.form['From'])
            db.session.add(subscriber)
            db.session.commit()
            output = "Thanks for contacting TWBC! Text 'subscribe' if you would like to receive updates via text message."
        else:
            output = _process_message(request.form['Body'], subscriber)
            db.session.commit()

        twilio_services = TwilioServices()
        return twiml(twilio_services.respond_message(output))

    def _process_message(message, subscriber):
        output = "Sorry, we don't recognize that command. Available commands are: 'subscribe' or 'unsubscribe'."

        if message.startswith(SUBSCRIBE_COMMAND) or message.startswith(UNSUBSCRIBE_COMMAND):
            subscriber.subscribed = message.startswith(SUBSCRIBE_COMMAND)

            if subscriber.subscribed:
                output = "You are now subscribed for updates."
            else:
                output = "You have unsubscribed from notifications. Text 'subscribe' to start receiving updates again"

        return output

    return views
marketing_notifications_python/views.py
Webhook to send message to all active subscribers

marketing_notifications_python/views.py

Let's take an even closer look at how we are sending SMS (or MMS) notifications.

Sending SMS or MMS Notifications

In the method send_message, we create a Twilio REST client that can be used to send SMS and MMS messages. The client requires your Twilio account credentials (an account SID and auth token), which can be found in the Twilio Console.

Next all we need to do is call create on the object in order to send our message. The Twilio Message API call requires a from_, to and a body parameters-- the media_url is optional.

Loading Code Samples...
Language
from marketing_notifications_python.twilio import account_sid, auth_token, phone_number
from twilio.twiml.messaging_response import MessagingResponse
from twilio.rest import Client


class TwilioServices:
    twilio_client = None

    def __init__(self):
        if TwilioServices.twilio_client is None:
            self.twilio_client = Client(account_sid(), auth_token())

    def send_message(self, to, message, image_url):
        self.twilio_client.messages.create(
            to=to,
            from_=phone_number(),
            body=message,
            media_url=image_url
        )

    def respond_message(self, message):
        response = MessagingResponse()
        response.message(message)
        return response
marketing_notifications_python/twilio/twilio_services.py
Use the Twilio Python Client to send a message

marketing_notifications_python/twilio/twilio_services.py

That's it! We've just implemented an opt-in process and an administrative interface to run an SMS and MMS marketing campaign with Python and Flask. Now all you need is killer content to share with your users via text or MMS.

We'll leave that part to you.  However, we will suggest a few other awesome features for your application on the next pane.

Where to Next?

We've got lots of Python and Flask guides on the site, but here are a couple of our favorites:

Appointment reminders

Automate the process of reaching out to your customers in advance of an upcoming appointment to drive down your no-show rate.

Click To Call

Convert web traffic into phone calls with the click of a button on your company's web site.

Did this help?

Thanks for checking out this tutorial! Tweet us @twilio to let us know what you thought or what you're building next.

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 marketing_notifications_python.models import app_db

db = app_db()


class Subscriber(db.Model):
    __tablename__ = "subscribers"

    id = db.Column(db.Integer, primary_key=True)
    phone_number = db.Column(db.String, nullable=False)
    subscribed = db.Column(db.Boolean, nullable=False, default=True)

    def __repr__(self):
        return '<Subscriber %r %r>' % self.phone_number, self.subscribed
from flask import request, flash
from marketing_notifications_python.forms import SendMessageForm
from marketing_notifications_python.models import init_models_module
from marketing_notifications_python.twilio import init_twilio_module
from marketing_notifications_python.view_helpers import twiml, view
from flask import Blueprint
from marketing_notifications_python.twilio.twilio_services import TwilioServices


def construct_view_blueprint(app, db):
    SUBSCRIBE_COMMAND = "subscribe"
    UNSUBSCRIBE_COMMAND = "unsubscribe"

    views = Blueprint("views", __name__)

    init_twilio_module(app)
    init_models_module(db)
    from marketing_notifications_python.models.subscriber import Subscriber

    @views.route('/', methods=["GET", "POST"])
    @views.route('/notifications', methods=["GET", "POST"])
    def notifications():
        form = SendMessageForm()
        if request.method == 'POST' and form.validate_on_submit():
            subscribers = Subscriber.query.filter(Subscriber.subscribed).all()
            if len(subscribers) > 0:
                flash('Messages on their way!')
                twilio_services = TwilioServices()
                for s in subscribers:
                    twilio_services.send_message(s.phone_number, form.message.data, form.imageUrl.data)
            else:
                flash('No subscribers found!')

            form.reset()
            return view('notifications', form)

        return view('notifications', form)

    @views.route('/message', methods=["POST"])
    def message():
        subscriber = Subscriber.query.filter(Subscriber.phone_number == request.form['From']).first()
        if subscriber is None:
            subscriber = Subscriber(phone_number=request.form['From'])
            db.session.add(subscriber)
            db.session.commit()
            output = "Thanks for contacting TWBC! Text 'subscribe' if you would like to receive updates via text message."
        else:
            output = _process_message(request.form['Body'], subscriber)
            db.session.commit()

        twilio_services = TwilioServices()
        return twiml(twilio_services.respond_message(output))

    def _process_message(message, subscriber):
        output = "Sorry, we don't recognize that command. Available commands are: 'subscribe' or 'unsubscribe'."

        if message.startswith(SUBSCRIBE_COMMAND) or message.startswith(UNSUBSCRIBE_COMMAND):
            subscriber.subscribed = message.startswith(SUBSCRIBE_COMMAND)

            if subscriber.subscribed:
                output = "You are now subscribed for updates."
            else:
                output = "You have unsubscribed from notifications. Text 'subscribe' to start receiving updates again"

        return output

    return views
from marketing_notifications_python.twilio import account_sid, auth_token, phone_number
from twilio.twiml.messaging_response import MessagingResponse
from twilio.rest import Client


class TwilioServices:
    twilio_client = None

    def __init__(self):
        if TwilioServices.twilio_client is None:
            self.twilio_client = Client(account_sid(), auth_token())

    def send_message(self, to, message, image_url):
        self.twilio_client.messages.create(
            to=to,
            from_=phone_number(),
            body=message,
            media_url=image_url
        )

    def respond_message(self, message):
        response = MessagingResponse()
        response.message(message)
        return response
from flask import request, flash
from marketing_notifications_python.forms import SendMessageForm
from marketing_notifications_python.models import init_models_module
from marketing_notifications_python.twilio import init_twilio_module
from marketing_notifications_python.view_helpers import twiml, view
from flask import Blueprint
from marketing_notifications_python.twilio.twilio_services import TwilioServices


def construct_view_blueprint(app, db):
    SUBSCRIBE_COMMAND = "subscribe"
    UNSUBSCRIBE_COMMAND = "unsubscribe"

    views = Blueprint("views", __name__)

    init_twilio_module(app)
    init_models_module(db)
    from marketing_notifications_python.models.subscriber import Subscriber

    @views.route('/', methods=["GET", "POST"])
    @views.route('/notifications', methods=["GET", "POST"])
    def notifications():
        form = SendMessageForm()
        if request.method == 'POST' and form.validate_on_submit():
            subscribers = Subscriber.query.filter(Subscriber.subscribed).all()
            if len(subscribers) > 0:
                flash('Messages on their way!')
                twilio_services = TwilioServices()
                for s in subscribers:
                    twilio_services.send_message(s.phone_number, form.message.data, form.imageUrl.data)
            else:
                flash('No subscribers found!')

            form.reset()
            return view('notifications', form)

        return view('notifications', form)

    @views.route('/message', methods=["POST"])
    def message():
        subscriber = Subscriber.query.filter(Subscriber.phone_number == request.form['From']).first()
        if subscriber is None:
            subscriber = Subscriber(phone_number=request.form['From'])
            db.session.add(subscriber)
            db.session.commit()
            output = "Thanks for contacting TWBC! Text 'subscribe' if you would like to receive updates via text message."
        else:
            output = _process_message(request.form['Body'], subscriber)
            db.session.commit()

        twilio_services = TwilioServices()
        return twiml(twilio_services.respond_message(output))

    def _process_message(message, subscriber):
        output = "Sorry, we don't recognize that command. Available commands are: 'subscribe' or 'unsubscribe'."

        if message.startswith(SUBSCRIBE_COMMAND) or message.startswith(UNSUBSCRIBE_COMMAND):
            subscriber.subscribed = message.startswith(SUBSCRIBE_COMMAND)

            if subscriber.subscribed:
                output = "You are now subscribed for updates."
            else:
                output = "You have unsubscribed from notifications. Text 'subscribe' to start receiving updates again"

        return output

    return views
from flask import request, flash
from marketing_notifications_python.forms import SendMessageForm
from marketing_notifications_python.models import init_models_module
from marketing_notifications_python.twilio import init_twilio_module
from marketing_notifications_python.view_helpers import twiml, view
from flask import Blueprint
from marketing_notifications_python.twilio.twilio_services import TwilioServices


def construct_view_blueprint(app, db):
    SUBSCRIBE_COMMAND = "subscribe"
    UNSUBSCRIBE_COMMAND = "unsubscribe"

    views = Blueprint("views", __name__)

    init_twilio_module(app)
    init_models_module(db)
    from marketing_notifications_python.models.subscriber import Subscriber

    @views.route('/', methods=["GET", "POST"])
    @views.route('/notifications', methods=["GET", "POST"])
    def notifications():
        form = SendMessageForm()
        if request.method == 'POST' and form.validate_on_submit():
            subscribers = Subscriber.query.filter(Subscriber.subscribed).all()
            if len(subscribers) > 0:
                flash('Messages on their way!')
                twilio_services = TwilioServices()
                for s in subscribers:
                    twilio_services.send_message(s.phone_number, form.message.data, form.imageUrl.data)
            else:
                flash('No subscribers found!')

            form.reset()
            return view('notifications', form)

        return view('notifications', form)

    @views.route('/message', methods=["POST"])
    def message():
        subscriber = Subscriber.query.filter(Subscriber.phone_number == request.form['From']).first()
        if subscriber is None:
            subscriber = Subscriber(phone_number=request.form['From'])
            db.session.add(subscriber)
            db.session.commit()
            output = "Thanks for contacting TWBC! Text 'subscribe' if you would like to receive updates via text message."
        else:
            output = _process_message(request.form['Body'], subscriber)
            db.session.commit()

        twilio_services = TwilioServices()
        return twiml(twilio_services.respond_message(output))

    def _process_message(message, subscriber):
        output = "Sorry, we don't recognize that command. Available commands are: 'subscribe' or 'unsubscribe'."

        if message.startswith(SUBSCRIBE_COMMAND) or message.startswith(UNSUBSCRIBE_COMMAND):
            subscriber.subscribed = message.startswith(SUBSCRIBE_COMMAND)

            if subscriber.subscribed:
                output = "You are now subscribed for updates."
            else:
                output = "You have unsubscribed from notifications. Text 'subscribe' to start receiving updates again"

        return output

    return views
from marketing_notifications_python.twilio import account_sid, auth_token, phone_number
from twilio.twiml.messaging_response import MessagingResponse
from twilio.rest import Client


class TwilioServices:
    twilio_client = None

    def __init__(self):
        if TwilioServices.twilio_client is None:
            self.twilio_client = Client(account_sid(), auth_token())

    def send_message(self, to, message, image_url):
        self.twilio_client.messages.create(
            to=to,
            from_=phone_number(),
            body=message,
            media_url=image_url
        )

    def respond_message(self, message):
        response = MessagingResponse()
        response.message(message)
        return response