SMS and MMS Marketing Notifications with Ruby and Sinatra

Download the Code

Ready to implement SMS and MMS marketing notifications in your Ruby and Sinatra application?

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

  1. A possible customer sends an SMS to a Twilio phone number you advertise somewhere.
  2. Your application confirms that the user wants to receive SMS and MMS notifications from your company.
  3. An administrator or marketing campaign manager uses a web form to craft a message that will go out to all 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 to manage interactions initiated by the user via SMS.
  • Messages Resource: We will use the REST API to broadcast messages out to all subscribers.
  • Sinatra Framework: We'll use Sinatra to structure the application.
  • DataMapper: We'll use DataMapper to define the model and to persist it.

Let's get started! Click the button below to move on to the next step of the tutorial.

The Subscriber Model

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

  • phone_number will store where to send the notifications.
  • subscribed lets the application identify which subscribers are active (only an active subscriber will receive notifications).
Loading Code Samples...
Language
require 'data_mapper'
require 'twilio-ruby'

class Subscriber
  include DataMapper::Resource

  property :id, Serial
  property :phone_number, String
  property :subscribed, Boolean

  def send_message(message, media_url=nil)
    @client = rest_client
    message_params = {
      from: ENV['TWILIO_NUMBER'],
      to: phone_number,
      body: message
    }

    message_params.merge!(media_url: media_url) unless media_url.nil? || media_url.empty?
    @client.messages.create(message_params)
  end

  def self.subscribed
    all(subscribed: true)
  end

  private

  def rest_client
    client = Twilio::REST::Client.new(
      ENV['TWILIO_ACCOUNT_SID'],
      ENV['TWILIO_AUTH_TOKEN']
    )
  end
end
model/subscriber.rb
The Subscriber model

model/subscriber.rb

Next up, let's see how to handle incoming messages.

Handle Incoming messages

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

It checks if the command sent in the message is one of the commands that we are expecting. Depending on that it returns a message formatted in TwiML - either a confirmation or a helpful message with our legal commands.

Loading Code Samples...
Language
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
app.rb
SMS and MMS Marketing Notifications

app.rb

Let's see how to create new subscribers next.

Create New Subscribers

On an incoming message, we look in the database for a matching number.  If there isn't one, we create a new Subscriber.  If it does, we validate the command from the message body and send an appropriate TwiML response.

And that's all we want at this step! We've created a Subscriber model to keep track of the people that have requested our messages. We also have saved their information on the database after receiving a text message with an 'add' command.

Loading Code Samples...
Language
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
app.rb
Register a new subscriber in the database

app.rb

Now let's look at how our users can manage their subscriptions.

Managing Subscriptions

We want to provide our users with two SMS commands to manage their subscription status: add and remove.

These commands will toggle a boolean flag for a Subscriber record in the database and will determine whether or not our user will receive messages from our marketing campaign. We don't opt them in automatically - rather, we have them 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 an add command or remove command, then create/update her or his subscription with the right status in the database.
  • If it is a command we don't recognize, then send a message explaining our available commands.
Loading Code Samples...
Language
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
app.rb
Validate and execute a subscriber's command

app.rb

Next up, let's look at how to send notifications.

Sending Notifications

When we receive a form submission from the frontend, we first grab the message text and optional image URL.  Second, we loop through all Subscribers and call the send_message method to send the message out.

When the messages are on their way, we render back the index page with a success message (and make the marketing team happy).

Loading Code Samples...
Language
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
app.rb
Webhook to send a message to all active subscribers

app.rb

Let's take an even closer look at how to send SMS or MMS notifications.

Send SMS or MMS Notifications

In the method send_message we create a Twilio REST API 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 console:

console credentials

 

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

Loading Code Samples...
Language
require 'data_mapper'
require 'twilio-ruby'

class Subscriber
  include DataMapper::Resource

  property :id, Serial
  property :phone_number, String
  property :subscribed, Boolean

  def send_message(message, media_url=nil)
    @client = rest_client
    message_params = {
      from: ENV['TWILIO_NUMBER'],
      to: phone_number,
      body: message
    }

    message_params.merge!(media_url: media_url) unless media_url.nil? || media_url.empty?
    @client.messages.create(message_params)
  end

  def self.subscribed
    all(subscribed: true)
  end

  private

  def rest_client
    client = Twilio::REST::Client.new(
      ENV['TWILIO_ACCOUNT_SID'],
      ENV['TWILIO_AUTH_TOKEN']
    )
  end
end
model/subscriber.rb
Use Twilio Ruby client to send a message

model/subscriber.rb

That's it! We've just implemented an opt-in process and an administrative interface to run an SMS and MMS marketing campaign.  

Next let's look at what other features you might like to deploy.

Where to Next?

Twilio and Ruby mix incredibly well.  To prove it, here are two excellent tutorials on how to add other features:

Employee Directory

Add an interface to your employee directory to look up contact information through your phone.

Automated Survey

Follow along with this simple tutorial to see the code necessary to implement automated surveys that integrate directly with your CRM and customer database.

Did this help?

Thanks for checking out this tutorial! Tweet @twilio to let us know what you think!

Jarod Reyes
Hector Ortega
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...
require 'data_mapper'
require 'twilio-ruby'

class Subscriber
  include DataMapper::Resource

  property :id, Serial
  property :phone_number, String
  property :subscribed, Boolean

  def send_message(message, media_url=nil)
    @client = rest_client
    message_params = {
      from: ENV['TWILIO_NUMBER'],
      to: phone_number,
      body: message
    }

    message_params.merge!(media_url: media_url) unless media_url.nil? || media_url.empty?
    @client.messages.create(message_params)
  end

  def self.subscribed
    all(subscribed: true)
  end

  private

  def rest_client
    client = Twilio::REST::Client.new(
      ENV['TWILIO_ACCOUNT_SID'],
      ENV['TWILIO_AUTH_TOKEN']
    )
  end
end
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
require 'sinatra'
require 'data_mapper'
require 'json'
require 'rack/contrib'
require 'tilt/erb'
require_relative 'model/subscriber'

database_url = 'postgres://postgres:postgres@localhost/marketing_notifications'
DataMapper.setup(:default, database_url)
DataMapper.finalize
Subscriber.auto_upgrade!

use ::Rack::PostBodyContentTypeParser

SUBSCRIPTION_MESSAGE = 'You are now subscribed for updates.'
UNSUBSCRIPTION_MESSAGE = %(
  You have unsubscribed from notifications.
  Test 'add' to start receiving updates again.
)
INSTRUCTIONS_MESSAGE = %(
  Thanks for contacting TWBC!
  Text 'add' if you would want to receive updates via text message.
)

get '/' do
  erb :index, locals: { message: nil }
end

post '/messages' do
  Subscriber.subscribed.each do |subscriber|
    subscriber.send_message(params['message'], params['image_url'])
  end

  erb :index, locals: { message: 'Messages Sent!!!' }
end

post '/subscriber' do
  command = params[:Body].downcase
  if valid_command?(command)
    subscriber = create_or_update_subscriber(params)

    if subscriber.subscribed
      format_message(SUBSCRIPTION_MESSAGE)
    else
      format_message(UNSUBSCRIPTION_MESSAGE)
    end
  else
    format_message(INSTRUCTIONS_MESSAGE)
  end
end

def subscription?(command)
  command == 'add'
end

def valid_command?(command)
  command == 'add' || command == 'remove'
end

def format_message(message)
  response = Twilio::TwiML::MessagingResponse.new
  response.message(body: message)
  response.to_s
end

def create_or_update_subscriber(params)
  subscriber = Subscriber.first(phone_number: params[:From])
  command = params[:Body].downcase
  if subscriber
    subscriber.update(subscribed: subscription?(command))
    subscriber
  else
    Subscriber.create(
      phone_number: params[:From],
      subscribed: subscription?(command)
    )
  end
end
require 'data_mapper'
require 'twilio-ruby'

class Subscriber
  include DataMapper::Resource

  property :id, Serial
  property :phone_number, String
  property :subscribed, Boolean

  def send_message(message, media_url=nil)
    @client = rest_client
    message_params = {
      from: ENV['TWILIO_NUMBER'],
      to: phone_number,
      body: message
    }

    message_params.merge!(media_url: media_url) unless media_url.nil? || media_url.empty?
    @client.messages.create(message_params)
  end

  def self.subscribed
    all(subscribed: true)
  end

  private

  def rest_client
    client = Twilio::REST::Client.new(
      ENV['TWILIO_ACCOUNT_SID'],
      ENV['TWILIO_AUTH_TOKEN']
    )
  end
end