SMS and MMS Marketing Notifications with PHP and Laravel

Ready to implement SMS and MMS marketing notifications?  Today we'll put the right pieces into place with PHP and Laravel.

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

  1. A potential customer sends an SMS to a Twilio phone number you've advertised 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 crafts a message in a web form to send to all subscribers.

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

Building Blocks

For this tutorial we'll be working with the following tools:

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

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

The Subscriber Class

In order to track who wants our marketing messages, let's start at the beginning and provide the right model:

  • phone_number stores where to send the marketing messages.
  • subscribed is a boolean which tracks if a Subscriber is opted in.  Unsubscribed users will not receive messages.
Loading Code Samples...
Language
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSubscribersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('subscribers', function (Blueprint $table) {
            $table->increments('id');
            $table->string('phone_number')->unique();
            $table->boolean('subscribed');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('subscribers');
    }
}
database/migrations/2015_12_07_191536_create_subscribers_table.php
The Subscriber model

database/migrations/2015_12_07_191536_create_subscribers_table.php

On deck: how we reply to incoming messages.

Handling Incoming Messages

This is the endpoint that will be called every time our number receives an incoming message from a new subscriber.

It relies on a createMessage method that produces the message that will be returned, resulting in a warm and fuzzy welcome message.

Loading Code Samples...
Language
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Twiml;

class SubscribersController extends Controller
{
    /**
     * Manage the subscription for a subscriber.
     *
     * @param Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $phoneNumber   = $request->input('From');
        $message       = $request->input('Body');
        $outputMessage = $this->createMessage($phoneNumber, $message);

        $response = new Twiml();
        $response->message($outputMessage);

        return response($response)
            ->header('Content-Type', 'text/xml');
    }

    private function createMessage($phone, $message)
    {
        $subscriber = Subscriber::where('phone_number', $phone)->first();
        if ($subscriber) {
            return $this->generateOutputMessage($subscriber, $message);
        }

        $subscriber = new Subscriber;
        $subscriber->phoneNumber = $phone;
        $subscriber->subscribed  = false;

        $subscriber->save();

        return trans('subscription.thanks');
    }

    private function generateOutputMessage($subscriber, $message)
    {
        $subscribe = 'add';
        $message = trim(strtolower($message));

        if (!$this->isValidCommand($message)) {
            return trans('subscription.unavailable_command');
        }

        $isSubscribed = starts_with($message, $subscribe);
        $subscriber->subscribed = $isSubscribed;
        $subscriber->save();

        return $isSubscribed
            ? trans('subscription.subscribed_confirmation')
            : trans('subscription.unsubscribed_confirmation');
    }

    private function isValidCommand($command)
    {
        return starts_with($command, 'add') || starts_with($command, 'remove');
    }
}
app/Http/Controllers/SubscribersController.php
Method to register a new subscriber

app/Http/Controllers/SubscribersController.php

Next we'll show how the createMessage method works.

Creating Subscribers

We begin by getting the user's phone number from the incoming Twilio request. Then we try to find a Subscriber model with that phone number.

If there's no subscriber with this phone number, we create one, save it, and respond with a message asking them to text the word "add". By default, users won't be subscribed; users will need to confirm the subscription with an additional text.

Loading Code Samples...
Language
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Twiml;

class SubscribersController extends Controller
{
    /**
     * Manage the subscription for a subscriber.
     *
     * @param Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $phoneNumber   = $request->input('From');
        $message       = $request->input('Body');
        $outputMessage = $this->createMessage($phoneNumber, $message);

        $response = new Twiml();
        $response->message($outputMessage);

        return response($response)
            ->header('Content-Type', 'text/xml');
    }

    private function createMessage($phone, $message)
    {
        $subscriber = Subscriber::where('phone_number', $phone)->first();
        if ($subscriber) {
            return $this->generateOutputMessage($subscriber, $message);
        }

        $subscriber = new Subscriber;
        $subscriber->phoneNumber = $phone;
        $subscriber->subscribed  = false;

        $subscriber->save();

        return trans('subscription.thanks');
    }

    private function generateOutputMessage($subscriber, $message)
    {
        $subscribe = 'add';
        $message = trim(strtolower($message));

        if (!$this->isValidCommand($message)) {
            return trans('subscription.unavailable_command');
        }

        $isSubscribed = starts_with($message, $subscribe);
        $subscriber->subscribed = $isSubscribed;
        $subscriber->save();

        return $isSubscribed
            ? trans('subscription.subscribed_confirmation')
            : trans('subscription.unsubscribed_confirmation');
    }

    private function isValidCommand($command)
    {
        return starts_with($command, 'add') || starts_with($command, 'remove');
    }
}
Register a new Subscriber if its phone number is not already in the database
Handle incoming messages with Laravel

Register a new Subscriber if its phone number is not already in the database

To recap: we've created a Subscriber model to keep track of the people that want our messages, and need to implement subscribing and opting-out.

Let's look at the subscribe/unsubscribe logic on the next pane.

Manage User Subscriptions

We want to provide the user 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 control that customer's marketing message preference.

To make this happen, the controller logic handles incoming messages from known Subscribers like this:

  • If it is a add or remove command, create/update their subscription with the right status in the database.
  • If it is a command we don't recognize, send them a message explaining available commands.
Loading Code Samples...
Language
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Twiml;

class SubscribersController extends Controller
{
    /**
     * Manage the subscription for a subscriber.
     *
     * @param Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $phoneNumber   = $request->input('From');
        $message       = $request->input('Body');
        $outputMessage = $this->createMessage($phoneNumber, $message);

        $response = new Twiml();
        $response->message($outputMessage);

        return response($response)
            ->header('Content-Type', 'text/xml');
    }

    private function createMessage($phone, $message)
    {
        $subscriber = Subscriber::where('phone_number', $phone)->first();
        if ($subscriber) {
            return $this->generateOutputMessage($subscriber, $message);
        }

        $subscriber = new Subscriber;
        $subscriber->phoneNumber = $phone;
        $subscriber->subscribed  = false;

        $subscriber->save();

        return trans('subscription.thanks');
    }

    private function generateOutputMessage($subscriber, $message)
    {
        $subscribe = 'add';
        $message = trim(strtolower($message));

        if (!$this->isValidCommand($message)) {
            return trans('subscription.unavailable_command');
        }

        $isSubscribed = starts_with($message, $subscribe);
        $subscriber->subscribed = $isSubscribed;
        $subscriber->save();

        return $isSubscribed
            ? trans('subscription.subscribed_confirmation')
            : trans('subscription.unsubscribed_confirmation');
    }

    private function isValidCommand($command)
    {
        return starts_with($command, 'add') || starts_with($command, 'remove');
    }
}
app/Http/Controllers/SubscribersController.php
Validate and apply an incoming subscriber command

app/Http/Controllers/SubscribersController.php

We'll visit the message creation form next.

Sending Marketing Messages

From the site's frontend, we retrieve the message text (and/or image URL) first.  Next we loop through all subscribers and call the sendMessage method to send the message out.

When the messages are on their way, we redirect the submitter back to the notifications.create route with a Flash Data message containing feedback about the messaging attempt.

Loading Code Samples...
Language
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Rest\Client;

class NotificationsController extends Controller
{
    protected $client;

    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    /**
     * Show the form for creating a notification.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('notifications.create');
    }

    public function send(Request $request)
    {
        $this->validate($request, ['message' => 'required']);

        $message  = $request->input('message');
        $imageUrl = $request->input('imageUrl');

        $activeSubscribers = Subscriber::active()->get();
        foreach ($activeSubscribers as $subscriber) {
            $this->sendMessage($subscriber->phoneNumber, $message, $imageUrl);
        }

        $request
            ->session()
            ->flash('status', 'Messages on their way!');

        return redirect()->route('notifications.create');
    }

    private function sendMessage($phoneNumber, $message, $imageUrl = null)
    {
        $twilioPhoneNumber = config('services.twilio')['phoneNumber'];
        $messageParams = array(
            'from' => $twilioPhoneNumber,
            'body' => $message
        );
        if ($imageUrl) {
            $messageParams['mediaUrl'] = $imageUrl;
        }

        $this->client->messages->create(
            $phoneNumber,
            $messageParams
        );
    }
}
app/Http/Controllers/NotificationsController.php
Webhook to send a message to all active subscribers

app/Http/Controllers/NotificationsController.php

Ready to see how we send SMS and MMS messages themselves?

Send SMS or MMS Marketing Messages

In the method sendMessage we create a Twilio REST API client that can be used to send either SMS or MMS messages.

The client requires your Twilio account credentials (an Account SID and Auth Token), which can be found in the Twilio 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 to parameter and an array containing a from number, the message body and an optional mediaUrl.

Loading Code Samples...
Language
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Rest\Client;

class NotificationsController extends Controller
{
    protected $client;

    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    /**
     * Show the form for creating a notification.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('notifications.create');
    }

    public function send(Request $request)
    {
        $this->validate($request, ['message' => 'required']);

        $message  = $request->input('message');
        $imageUrl = $request->input('imageUrl');

        $activeSubscribers = Subscriber::active()->get();
        foreach ($activeSubscribers as $subscriber) {
            $this->sendMessage($subscriber->phoneNumber, $message, $imageUrl);
        }

        $request
            ->session()
            ->flash('status', 'Messages on their way!');

        return redirect()->route('notifications.create');
    }

    private function sendMessage($phoneNumber, $message, $imageUrl = null)
    {
        $twilioPhoneNumber = config('services.twilio')['phoneNumber'];
        $messageParams = array(
            'from' => $twilioPhoneNumber,
            'body' => $message
        );
        if ($imageUrl) {
            $messageParams['mediaUrl'] = $imageUrl;
        }

        $this->client->messages->create(
            $phoneNumber,
            $messageParams
        );
    }
}
app/Http/Controllers/NotificationsController.php
Use the Twilio PHP client to send a message

app/Http/Controllers/NotificationsController.php

And with that, the application is completely wired.  Next up, we'll look at what other features you can easily add with Twilio and a little guidance from our tutorials.

Where to Next?

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

Now all you need is killer content to share with your users via text or MMS (That's on you).

PHP and Twilio are a great mix.  Here are just a couple of other features we think you'll enjoy:

IVR: Phone Tree

Easily route callers to the right people and information with an IVR (interactive voice response) system.

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

Did this help?

If you have any feedback to share with us please tweet to us @twilio.  We love to hear what you've built and what you're building!

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...
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSubscribersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('subscribers', function (Blueprint $table) {
            $table->increments('id');
            $table->string('phone_number')->unique();
            $table->boolean('subscribed');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('subscribers');
    }
}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Twiml;

class SubscribersController extends Controller
{
    /**
     * Manage the subscription for a subscriber.
     *
     * @param Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $phoneNumber   = $request->input('From');
        $message       = $request->input('Body');
        $outputMessage = $this->createMessage($phoneNumber, $message);

        $response = new Twiml();
        $response->message($outputMessage);

        return response($response)
            ->header('Content-Type', 'text/xml');
    }

    private function createMessage($phone, $message)
    {
        $subscriber = Subscriber::where('phone_number', $phone)->first();
        if ($subscriber) {
            return $this->generateOutputMessage($subscriber, $message);
        }

        $subscriber = new Subscriber;
        $subscriber->phoneNumber = $phone;
        $subscriber->subscribed  = false;

        $subscriber->save();

        return trans('subscription.thanks');
    }

    private function generateOutputMessage($subscriber, $message)
    {
        $subscribe = 'add';
        $message = trim(strtolower($message));

        if (!$this->isValidCommand($message)) {
            return trans('subscription.unavailable_command');
        }

        $isSubscribed = starts_with($message, $subscribe);
        $subscriber->subscribed = $isSubscribed;
        $subscriber->save();

        return $isSubscribed
            ? trans('subscription.subscribed_confirmation')
            : trans('subscription.unsubscribed_confirmation');
    }

    private function isValidCommand($command)
    {
        return starts_with($command, 'add') || starts_with($command, 'remove');
    }
}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Twiml;

class SubscribersController extends Controller
{
    /**
     * Manage the subscription for a subscriber.
     *
     * @param Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $phoneNumber   = $request->input('From');
        $message       = $request->input('Body');
        $outputMessage = $this->createMessage($phoneNumber, $message);

        $response = new Twiml();
        $response->message($outputMessage);

        return response($response)
            ->header('Content-Type', 'text/xml');
    }

    private function createMessage($phone, $message)
    {
        $subscriber = Subscriber::where('phone_number', $phone)->first();
        if ($subscriber) {
            return $this->generateOutputMessage($subscriber, $message);
        }

        $subscriber = new Subscriber;
        $subscriber->phoneNumber = $phone;
        $subscriber->subscribed  = false;

        $subscriber->save();

        return trans('subscription.thanks');
    }

    private function generateOutputMessage($subscriber, $message)
    {
        $subscribe = 'add';
        $message = trim(strtolower($message));

        if (!$this->isValidCommand($message)) {
            return trans('subscription.unavailable_command');
        }

        $isSubscribed = starts_with($message, $subscribe);
        $subscriber->subscribed = $isSubscribed;
        $subscriber->save();

        return $isSubscribed
            ? trans('subscription.subscribed_confirmation')
            : trans('subscription.unsubscribed_confirmation');
    }

    private function isValidCommand($command)
    {
        return starts_with($command, 'add') || starts_with($command, 'remove');
    }
}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Twiml;

class SubscribersController extends Controller
{
    /**
     * Manage the subscription for a subscriber.
     *
     * @param Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $phoneNumber   = $request->input('From');
        $message       = $request->input('Body');
        $outputMessage = $this->createMessage($phoneNumber, $message);

        $response = new Twiml();
        $response->message($outputMessage);

        return response($response)
            ->header('Content-Type', 'text/xml');
    }

    private function createMessage($phone, $message)
    {
        $subscriber = Subscriber::where('phone_number', $phone)->first();
        if ($subscriber) {
            return $this->generateOutputMessage($subscriber, $message);
        }

        $subscriber = new Subscriber;
        $subscriber->phoneNumber = $phone;
        $subscriber->subscribed  = false;

        $subscriber->save();

        return trans('subscription.thanks');
    }

    private function generateOutputMessage($subscriber, $message)
    {
        $subscribe = 'add';
        $message = trim(strtolower($message));

        if (!$this->isValidCommand($message)) {
            return trans('subscription.unavailable_command');
        }

        $isSubscribed = starts_with($message, $subscribe);
        $subscriber->subscribed = $isSubscribed;
        $subscriber->save();

        return $isSubscribed
            ? trans('subscription.subscribed_confirmation')
            : trans('subscription.unsubscribed_confirmation');
    }

    private function isValidCommand($command)
    {
        return starts_with($command, 'add') || starts_with($command, 'remove');
    }
}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Rest\Client;

class NotificationsController extends Controller
{
    protected $client;

    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    /**
     * Show the form for creating a notification.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('notifications.create');
    }

    public function send(Request $request)
    {
        $this->validate($request, ['message' => 'required']);

        $message  = $request->input('message');
        $imageUrl = $request->input('imageUrl');

        $activeSubscribers = Subscriber::active()->get();
        foreach ($activeSubscribers as $subscriber) {
            $this->sendMessage($subscriber->phoneNumber, $message, $imageUrl);
        }

        $request
            ->session()
            ->flash('status', 'Messages on their way!');

        return redirect()->route('notifications.create');
    }

    private function sendMessage($phoneNumber, $message, $imageUrl = null)
    {
        $twilioPhoneNumber = config('services.twilio')['phoneNumber'];
        $messageParams = array(
            'from' => $twilioPhoneNumber,
            'body' => $message
        );
        if ($imageUrl) {
            $messageParams['mediaUrl'] = $imageUrl;
        }

        $this->client->messages->create(
            $phoneNumber,
            $messageParams
        );
    }
}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Subscriber;

use Twilio\Rest\Client;

class NotificationsController extends Controller
{
    protected $client;

    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    /**
     * Show the form for creating a notification.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('notifications.create');
    }

    public function send(Request $request)
    {
        $this->validate($request, ['message' => 'required']);

        $message  = $request->input('message');
        $imageUrl = $request->input('imageUrl');

        $activeSubscribers = Subscriber::active()->get();
        foreach ($activeSubscribers as $subscriber) {
            $this->sendMessage($subscriber->phoneNumber, $message, $imageUrl);
        }

        $request
            ->session()
            ->flash('status', 'Messages on their way!');

        return redirect()->route('notifications.create');
    }

    private function sendMessage($phoneNumber, $message, $imageUrl = null)
    {
        $twilioPhoneNumber = config('services.twilio')['phoneNumber'];
        $messageParams = array(
            'from' => $twilioPhoneNumber,
            'body' => $message
        );
        if ($imageUrl) {
            $messageParams['mediaUrl'] = $imageUrl;
        }

        $this->client->messages->create(
            $phoneNumber,
            $messageParams
        );
    }
}