How to Send SMS Reminders with Laravel Lumen

November 21, 2018
Written by
Charles Oduk
Contributor
Opinions expressed by Twilio contributors are their own

Lumen Reminders.png

Introduction

In the previous tutorial, we worked on creating an iCal feed using Laravel Lumen. An iCal feed allows users to export a calendar event and view it on an external calendar. In this tutorial, we will take an extra step. Using the Twilio API, we will send SMS reminders to users who subscribed to an event.

We are going to extend the application used to create an iCal feed, therefore you need to have followed the previous tutorial. Otherwise, you can clone the code from Github.

Requirements

Setup

Install Dependencies

We need to install the Twilio SDK for PHP, which makes it easy to interact with the Twilio API from our Lumen application. In your terminal, within the project directory, run the command:

$ composer require twilio/sdk

Update the .env

In the root directory, we have a .env file. Previously we updated it with our database credentials.

After installing the Twilio SDK, we need to update our .env file with three Twilio credentials:

TWILIO_SID="INSERT YOUR TWILIO SID HERE"
TWILIO_TOKEN="INSERT YOUR TWILIO TOKEN HERE"
TWILIO_NUMBER="INSERT YOUR TWILIO NUMBER IN E.164 FORMAT"

You can find the credentials on your Twilio dashboard:

Create the Subscribers Migration

We previously had one table, tech_events. We need to create a new table for subscribers and link it to the tech_events table. Each subscriber should be linked to a tech event.

Let's go ahead and run the command:

$ php artisan make:migration create_subscribers_table

We can find our migration in the database/migrations folder. In our newly created migration, let’s add the following lines of code:

<?php

use Illuminate\Support\Facades\Schema;
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('first_name');
           $table->string('last_name');
           $table->string('phone_number');
           $table->integer('tech_events_id')->unsigned();
           $table->timestamps();

           $table->foreign('tech_events_id')->references('id')->on('tech_events');
       });
   }

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

We have added the fields: first_name, last_name, phone_number and tech_events_id.

The key thing to note from the code above is that the field tech_events_id, has a foreign key relationship with the id field in the tech_events table.

To create the new subscribers table and update foreign keys, run the command:

$ php artisan migrate

Let’s check our database. We should now have a new table called subscribers. The fields we made in the migration should be reflected in the table. Using Postico, this is what the database looks like:

 

Create the Subscribers Model

Our models are currently in the app directory as single files. Since we’re adding one more model, this is a great opportunity for us to organize them by putting them in a Models folder.

Once we have moved our models to the Models folder, let’s create a new model called Subscribe.php.

This is what our directory looks like:

For the files we just moved to the Models folder, remember to change the namespace to: namespace App\Models;

In the newly created Subscriber.php file, let’s add the following code:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Subscriber extends Model
{
   protected $table = "subscribers";

   /**
    * The attributes that are mass assignable.
    *
    * @var array
    */
   protected $fillable = [
       "first_name",
       "last_name",
       "phone_number",
       "tech_events_id"
   ];
  
   /**
    * The rules for data entry
    *
    * @var array
    */
   public static $rules = [
       "first_name" => "required",
       "last_name" => "required",
       "phone_number" => "required",
       "tech_events_id" => "required",
   ];

  
   /**
    * Defines the relationship with TechEvents
    *
    * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
    */
   public function events()
   {
       return $this->belongsTo("App/Models/TechEvents");
   }
}

In this model, we have the mass assignable fields and the rules for assigning values to said fields. Lastly, we have indicated the relationship to the TechEvents model.

We also need to update the TechEvents model in order to define the relationship with the Subscriber model. A tech event can have many subscribers. In the file app/Models/techEvents.php, inside the TechEvents class, let’s add this piece of code:

   /**
    * Defines the relationship to the Subscriber model
    *
    * @return \Illuminate\Database\Eloquent\Relations\HasMany
    */
   public function subscribers()
   {
       return $this->hasMany("App\Models\Subscriber");
   }

Seed the Subscribers Table

Before we can get to the logic of sending SMS reminders, we need to have numbers or at least one number in the database. For this tutorial, I am using a trial account so we can only send text messages to verified numbers. We will go ahead and add that number to the database. If you haven’t created a verified number, please do so on the verified numbers dashboard.

In the previous tutorial, we used Model Factories to create fake data. In this tutorial we are going to do it slightly differently. We need to enter an actual verified phone number, so we will create a seeder from scratch.

In the app/database/seeds directory, create a file called SubscriberTableSeeder.php and add the following code:

<?php

use Illuminate\Database\Seeder;
use Carbon\Carbon;

class SubscriberTableSeeder extends Seeder
{
   /**
    * Run the database seeds.
    *
    * @return void
    */
   public function run()
   {
       DB::table("subscribers")->insert(
           [
               "first_name" => "First",
               "last_name" => "Last",
               "phone_number" => "INSERT VERIFIED NUMBER",
               "tech_events_id" => 1,
               "created_at" => Carbon::now(),
               "updated_at" => Carbon::now()
           ]
       );
   }
}

That was easy! If you are using a trial account, make sure the number you entered in the phone_number field is a verified number otherwise the SMS will not be sent.

We need one more step to make this available for seeding. Let’s update the file app/database/seeds/DatabaseSeeder.php with the following piece of code:

<?php

use Illuminate\Database\Seeder;
use App\Models\TechEvents;

class DatabaseSeeder extends Seeder
{
   /**
    * Run the database seeds.
    *
    * @return void
    */
   public function run()
   {
       factory(TechEvents::class, 10)->create();
       $this->call("SubscriberTableSeeder");
   }
}

The run method in this file is called when we run the command php artisan db:seed. We have added the line:

  $this->call("SubscriberTableSeeder");

This calls the seeder file we just created and seeds the subscribers table with the data we entered. It is important to note that on line 4, we have changed the import from use App\TechEvents; to use App\Models\TechEvents;. This is because we made a change earlier and put our models inside the Models folder.

Let’s go ahead and make similar changes in the file app/database/factories/ModelFactory.php as well. Here’s the updated file:

<?php
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| Here you may define all of your model factories. Model factories give
| you a convenient way to create models for testing and seeding your
| database. Just tell the factory how a default model should look.
|
*/

use Carbon\Carbon;

$factory->define(App\Models\User::class, function (Faker\Generator $faker) {
   return [
       "name" => $faker->name,
       "email" => $faker->email,
   ];
});

$factory->define(App\Models\TechEvents::class, function (Faker\Generator $faker) {
   $startTime = Carbon::createFromTimeStamp($faker->dateTimeBetween("now", "+1 month")->getTimestamp());
   return [
       "name" => $faker->word,
       "starts" => $startTime,
       "ends" => Carbon::createFromFormat("Y-m-d H:i:s", $startTime)->addHours(2),
       "status" => "CONFIRMED",
       "summary" => $faker->sentence($nbWords = 6, $variableNbWords = true),
       "location" => $faker->word,
       "uid" => $faker->domainName,
       "created_at" => Carbon::now()->toDateTimeString(),
       "updated_at" => Carbon::now()->toDateTimeString()
   ];
});

That’s everything we need to do. If you are working with a fresh database, go ahead and run the command:

$ php artisan db:seed

If you are following from the previous tutorial and you already have data in the database, run the command:

$ php artisan db:seed --class=SubscriberTableSeeder

That only seeds data to the Seeders table. Great work! We have now defined the subscribers table and seeded data. Now we’re ready to write the logic for sending reminders.

The Code

For this tutorial, SMS reminders will only be sent two days prior to the event. To accomplish this, we are going to create a command and schedule it to run daily. Since it is good practice to keep console commands light, we will create a reminder client that handles the logic and let the command defer to it.

Create a Reminder Client

In the app folder, let’s create a folder and name it Clients. Inside the folder, let’s create a file called EventReminderClient.php with the following code:

<?php

namespace App\Clients;

use Twilio\Rest\Client as TwilioClient;
use App\Models\TechEvents;
use Carbon\Carbon;

class EventReminderClient
{
   /**
    * Create a new instance
    *
    * @return void
    */
   public function __construct()
   {
       $twilioAccountSid   = getenv("TWILIO_SID");
       $twilioAuthToken    = getenv("TWILIO_TOKEN");

       $this->twilioClient = new TwilioClient($twilioAccountSid, $twilioAuthToken);
   }

   /**
    * Get's events that have subscribers and are due
    * in 2 days, then sends reminders.
    *
    * @return string
    */
   public function sendEventReminders()
   {
       $events = TechEvents::has('subscribers')->get();

       $eventsInTwoDays = $this->getEventsDueInTwoDays($events);
       if (empty($eventsInTwoDays)) {
           return "There are no events due in two days";
       }

       $subscribers = $this->getSubscribersInformation($eventsInTwoDays);

       return $this->sendTwilioSmsReminders($subscribers);
   }

   /**
    * Get events that are due in two days
    *
    * @param object $events    - Events that have subscribers
    *
    * @return array $eventsDue - Events due in 2 days
    */
   private function getEventsDueInTwoDays($events)
   {
       $today = Carbon::now()->toDateTimeString();

       $eventsDue = [];
  
       foreach ($events as $event) {
           $daysToEvent = round((strtotime($event->starts) - strtotime($today)) / 86400, 0);

           if ($daysToEvent === 2.0 ) {
               array_push($eventsDue, $event);
           }
       }
       return $eventsDue;
   }
  
   /**
    * Get the subscriber information and corresponding event name
    *
    * @param  array $events      - Array of events due in 2 days
    *
    * @return array $subscribers - Array of subscribers info and event name
    *
    */
   private function getSubscribersInformation($events)
   {
       $subscribers = [];

       foreach ($events as $event) {
           foreach ($event->subscribers as $subscriber) {
               $subscribers[] = [
                   $subscriber->phone_number,
                   $subscriber->first_name,
                   $event->name
               ];
           }
       }

       return $subscribers;

   }
  
   /**
    * Send messages using Twilio API client
    *
    * @param array $subscribers - Subscribers info
    *
    * @return void
    */
   public function sendTwilioSmsReminders($subscribers)
   {
       $myTwilioNumber = getenv("TWILIO_NUMBER");
       foreach( $subscribers as $subscriber) {
           $this->twilioClient->messages->create(
               // Where to send a text message
               $subscriber[0],
               array(
                   "from" => $myTwilioNumber,
                   "body" => "Hey! ". $subscriber[1] . ", the ".$subscriber[2] ." Tech event begins in 2 days!"
               )
           );
       }
       return "Successfully sent ". count($subscribers) . " reminder(s)";
   }
}

In the constructor we have initialized the Twilio credentials we added to our .env earlier. We have also created a Twilio Rest Client, which we’ll use to send messages.

sendEventReminders() is the main method in the Event Reminder Client. This method will be called from the command we create in the next step.

The first line in the method fetches all events that have subscribers. In our case, it is only one. Secondly, it calls the getEventsDueInTwoDays($events) method which loops through the events and checks if the event is due in two days.

Once we have the events that are due in two days, the next step is to retrieve the subscribers information in order to send them SMS reminders.

Create the SMS Reminder Command

Creating a command makes it possible to schedule our logic to run as often as we require.

In the app/Console/Commands directory, let’s create a new file called, SendSmsReminderCommand.php and add the following code:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

use App\Clients\EventReminderClient;

class SendSmsReminderCommand extends Command
{
   /**
    * The name and signature of the console command.
    *
    * @var string
    */
   protected $signature = "send:sms-reminder";
   /**
    * The console command description.
    *
    * @var string
    */
   protected $description = "This will send a reminder 2 days before the event";

   protected $eventReminderClient;

   /**
    * Create a new command instance.
    *
    * @return void
    */
   public function __construct(EventReminderClient $eventReminderClient)
   {
       parent::__construct();
       $this->eventReminderClient = $eventReminderClient;

   }
   /**
    * Execute the console command.
    *
    * @return mixed
    */
   public function handle()
   {
       $this->eventReminderClient->sendEventReminders();
   }
}

We have just created an Artisan Console Command. For more information on how to create commands, have a look at the official documentation.

Register and Schedule Command

Now that we have created our command, we need to register, test and schedule it.

In the app/Console/Kernel.php file, lets add the following code:

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
   /**
    * The Artisan commands provided by your application.
    *
    * @var array
    */
   protected $commands = [
       Commands\SendSmsReminderCommand::class,
   ];

   /**
    * Define the application"s command schedule.
    *
    * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
    * @return void
    */
   protected function schedule(Schedule $schedule)
   {
       $schedule->command("send:sms-reminder")->daily();
   }
}

We have registered our command and scheduled it to run daily at midnight. You can check the documentation for more options on scheduling.

Test our Code

In order to test our command, we should run this in our terminal:

$ php artisan send:sms-reminder

Please remember that we entered random dates for events using Model Factories. You may therefore not have an event that has a subscriber and is due in two days. This is what I get when I first run the command:

In order to test this, we need to manually change the start date of the first event in our tech_events table. Make it two days from today. This time, when you run the command, this is what you get:

You will also receive a text message on your verified phone number.

You have successfully scheduled a reminder for your subscribers!

Conclusion

Now that you have learned how to create an iCal feed and send SMS reminders, an obvious next step is to complete your own event scheduling application. If you’d like to check out the complete code, please do so on Github. The work we have done in this tutorial is available in the send-sms-reminder branch.

I look forward to seeing what you build. You can reach me on: