In this tutorial, we’ll be building an SMS application that sends out spoiler alerts to our friends every week after the show has been aired. We’ll be making use of Twilio’s Copilot feature to learn how we can send outbound SMS to a large number of people at once.
Technical Requirements
To follow along, you’ll need the following:
- PHP version 7.1 or Higher
- Laravel 5.8
- Composer
- A Twilio Account
Create New Laravel Project
Let’s install a new Laravel project via the Composer create-project
command. From your terminal, run the following command:
$ composer create-project --prefer-dist laravel/laravel twilio-got-spoiler
This will install a new Laravel project for us in the twilio-got-spoiler
directory.
Creating Models and Migrations
We’ll be needing two migrations. One for storing the spoilers to be sent out and the other for storing the phone numbers that should receive those spoilers.
From your terminal, run the following command:
$ php artisan make:model Spoiler -m
$ php artisan make:model PhoneNumber -m
Tip: The -m flag will create a migration file associated with the model.
Next, let’s edit the migration
files to reflect the structure each of those tables will need. Head over to the database/migrations
directory and edit the spoiler
migration
with the following code:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSpoilersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('spoilers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('message');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('spoilers');
}
}
Also, edit the phone_number
migration
with the following code:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePhoneNumbersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('phone_numbers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('phone_number');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('phone_numbers');
}
}
Next, edit the .env
file with your database details and execute the following command to run all of our migrations
.
$ php artisan migrate
Also, it’s important that we identify the attributes
on our models
that can be mass assignable. Add the following code to the Spoiler
and PhoneNumber
models respectively. They are located in the app
folder.
protected $fillable = ['message'];
protected $fillable = ['phone_number'];
Saving Spoilers and Phone Numbers
Next, we’ll add the routes for our application that allow us to save spoiler messages, as well as register phone numbers. Edit the routes/web.php
file with the following code:
Route::get('/', 'HomeController@index');
Route::post('/spoiler/create', 'HomeController@storeSpoiler')->name('create.spoiler');
Route::post('/phone-number/create', 'HomeController@storePhoneNumber')->name('create.phone.number');
To add the corresponding controller
methods, from the terminal, run the following command to create a new controller
:
$ php artisan make:controller HomeController
Next, head over to the app/Http/Controllers
directory and edit the HomeController
file we just created with the following code:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Spoiler;
use App\PhoneNumber;
class HomeController extends Controller
{
public function index()
{
$spoilers = Spoiler::latest()->get();
$phoneNumbers = PhoneNumber::all();
return view('home', compact('spoilers', 'phoneNumbers'));
}
public function storeSpoiler(Request $request, Spoiler $spoiler)
{
$this->validate($request, [
'message' => 'required'
]);
$spoiler->create($request->only(['message']));
return back()->with('success', 'Spoiler has been added successfully');
}
public function storePhoneNumber(Request $request, PhoneNumber $number)
{
$this->validate($request, [
'phone_number' => 'required'
]);
$number->create($request->only(['phone_number']));
return back()->with('success', 'Phone Number has been added successfully');
}
}
In the index
method, we query the database
to fetch all the spoilers and phone numbers we’ve created - which is none so far - and then pass them down to a view
file we’ll be creating shortly.
The storeSpoiler
method validates the incoming request to ensure a message
field is present and not empty, and then stores the spoiler messages to be sent out.
The storePhoneNumbers
method also performs a validation check to ensure the phone_number
field is required and then stores the phone number.
Building the Frontend
Let’s create the simple view our application will have. We’ll be needing two forms. One for creating the spoiler messages and the other for registering the phone numbers. In the resources/views
directory, create a home.blade.php
file and add the following code to the file:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GOT Spoiler With Twilio</title>
<!-- Bootstrap styles CDN -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="jumbotron">
@include('alert')
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
Add Spoiler
</div>
<div class="card-body">
<form action="{{route('create.spoiler')}}" method="post">
@csrf
<div class="form-group">
<label>Enter Spoiler</label>
<input type="text" name="message" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Add Spoiler</button>
</form>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-header">
Spoiler Messages
</div>
<div class="card-body">
<form>
<div class="form-group">
<label>All Spoilers</label>
<select multiple class="form-control">
@foreach($spoilers as $spoiler)
<option>{{$spoiler->message}}</option>
@endforeach
</select>
</div>
</form>
</div>
</div>
</div>
</div>
<br>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
Add Phone Numbers
</div>
<div class="card-body">
<form action="{{route('create.phone.number')}}" method="post">
@csrf
<div class="form-group">
<label>Register Phone Number</label>
<input type="tel" name="phone_number" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Add Number</button>
</form>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-header">
Phone Numbers
</div>
<div class="card-body">
<form>
<div class="form-group">
<label>All Phone Numbers</label>
<select multiple class="form-control">
@foreach($phoneNumbers as $phoneNumber)
<option> {{$phoneNumber->phone_number}}</option>
@endforeach
</select>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
If you take a closer look at the code above, you’ll notice we included an alert partial
with the following line @include('alert')
. Let’s create the partial
in the resources/views
directory by creating a alert.blade.php
file and add the following code to the file:
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
This partial
is used to display any message that has been flashed into the session
.
Next, navigate to the app’s homepage and you should see the following page:
Setting up Twilio
We will be using the Twilio SDK for PHP library to interact with Twilio’s API. Run the following command to install the library:
$ composer require twilio/sdk
Handling Bulk SMS using Copilot
We can safely assume that we’ll be sending out spoiler outbound SMS messages to hundreds of users at once. To make this process easy and seamless, Twilio provides a feature called Copilot which is perfect for bulk messaging.
Before we can make use of Twilio’s Copilot, we first need to create a new Messaging Service. Head over to your Twilio dashboard to create one. Next, you’ll be prompted with a modal similar to the one below.
After you’ve created the messaging service, take note of the Service SID
as we’ll be needing it shortly.
Also, it’s important to note that we’ll need to add phone numbers, short codes or an Alpha Sender ID to the Messaging Service we just created. This helps Copilot to select an identity to use when sending our messages.
Environment and Config Variables
Edit the .env
file with your Twilio credentials:
TWILIO_ACCOUNT_SID=xxxxx
TWILIO_AUTH_TOKEN=xxxxx
TWILIO_MESSAGING_SERVICE_SID=xxxxx
Next, we’ll add a new Twilio array in the config/services.php
file so that we can easily reference the environment variables we just defined.
'twilio' => [
'account_sid' => env('TWILIO_ACCOUNT_SID'),
'auth_token' => env('TWILIO_AUTH_TOKEN'),
'messaging_service_sid' => env('TWILIO_MESSAGING_SERVICE_SID')
]
Creating a Twilio Wrapper
Since our Messaging Service has been configured, we are ready to start sending spoilers. We’ll be using a Twilio wrapper that we’ll create shortly to interact with the Twilio SDK library we installed earlier. Create a new Twilio.php
file under the app\Services
directory and then add the following code:
<?php
namespace App\Services;
use Twilio\Rest\Client;
class Twilio
{
protected $account_sid;
protected $auth_token;
protected $messagingServiceSid;
protected $client;
/**
* Create a new instance
*
* @return void
*/
public function __construct()
{
$this->account_sid = config('services.twilio.account_sid');
$this->auth_token = config('services.twilio.auth_token');
$this->messagingServiceSid = config('services.twilio.messaging_service_sid');
$this->client = $this->setUp();
}
public function setUp()
{
$client = new Client($this->account_sid, $this->auth_token);
return $client;
}
public function notify($number, $spoiler)
{
$message = $this->client->messages->create($number, [
'body' => $spoiler,
'messagingServiceSid' => $this->messagingServiceSid
]);
return $message;
}
}
In the constructor, we initialized the Twilio credentials we added to our config\services.php
file earlier. We also created a new Twilio Rest Client and assigned it to the client
property of our class.
The notify
method handles sending outbound SMS using the Twilio API. It accepts two arguments
, the phone number that we’ll be sending the spoiler too as well as the spoiler message itself.
Creating a Job
We’ll make use of a Job to handle dispatching the spoiler alerts to all the phone numbers registered in our application. Run the following command:
$ php artisan make:job SendGotSpoiler
This will create a new class for us in the app/Jobs
directory. Edit the file with the following code:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Spoiler;
use App\PhoneNumber;
use App\Services\Twilio;
class SendGotSpoiler implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $spoiler;
protected $twilio;
protected $phoneNumbers;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Twilio $twilio)
{
$this->spoiler = Spoiler::latest()->first();
$this->phoneNumbers = PhoneNumber::all();
$this->twilio = $twilio;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$twilio = $this->twilio;
$this->phoneNumbers->map(function ($phoneNumber) use ($twilio) {
$twilio->notify($phoneNumber->phone_number, $this->spoiler->message);
});
}
}
Let’s break this class
into parts so that we can understand what’s happening here.
public function __construct(Twilio $twilio)
{
$this->spoiler = Spoiler::latest()->first();
$this->phoneNumbers = PhoneNumber::all();
$this->twilio = $twilio;
}
Here, the constructor
accepts the Twilio class we created in the previous section as a dependency
. We fetch the most recent spoiler that has been created and assign it to the spoiler property
of the class
. Similarly, we fetch all the registered phone numbers as well and then assign them to the phone numbers property
of the class.
public function handle()
{
$twilio = $this->twilio;
$this->phoneNumbers->map(function ($phoneNumber) use ($twilio) {
$twilio->notify($phoneNumber->phone_number, $this->spoiler->message);
});
}
In the handle
method, we map all the registered phone numbers and then send the spoiler message to each of them.
Creating a Custom Command
Let’s create a custom command that we can use via the terminal to trigger sending the spoiler messages. Run the following command:
$ php artisan make:command SendGotSpoilerCommand
This will create a SendGotSpoilerCommand.php
file for us in the app/Console/Commands
directory. Edit the file with the following code:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Jobs\SendGotSpoiler;
class SendGotSpoilerCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'send-got-spoiler';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send Game of Thrones Spoiler';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle(SendGotSpoiler $sendGotSpoiler)
{
$sendGotSpoiler->handle();
$this->comment('Hehe, Spoilers have been sent');
}
}
In this class
we import the SendGotSpoiler
job, and then pass it as a dependency
to the handle
method of the class
. We then call the handle
method on the SendGotSpoiler
job and then display a comment to let us know when the command has finished executing.
Testing
It’s time to finally test what we’ve been building so far. Head over to the homepage and create a spoiler. Then register the phone number that should receive the spoiler.
Next, run the following command to send the spoiler:
$ php artisan send-got-spoiler
You should see the following
You should receive the following SMS
Taking it Further
My spoiler of choice is for Game of Thrones. Since it aired every Sunday, you can go ahead and schedule a cron job that automatically calls the send-got-spoiler
command every Sunday. Note that you’ll always have to create the latest spoiler yourself after the episode has been aired.
Conclusion
In this tutorial, we’ve built a simple spoiler application that sends out spoiler alerts to our friends. We’ve also learned how to send bulk SMS to a group of people at once using Twilio’s Copilot. You can find the repo on Github.
Dotun Jolaoso
Twitter: @dotunj_
Github: @dotunj