Create a Database Queue to Send SMS in PHP with Laravel Queues and Twilio SMS

September 30, 2019
Written by
Michael Okoko
Contributor
Opinions expressed by Twilio contributors are their own

Create a Database Queue to Send SMS in PHP with Laravel Queues and Twilio SMS.png

Queues are ways in which we enable our application to listen and act based on predefined events. They allow us to delay tasks that would otherwise interfere with the user experience or our application’s performance.  

From the Laravel docs, "Laravel queues provide a unified API across a variety of different queue backends, such as Beanstalk, Amazon SQS, Redis, or even a relational database."

What are we Building?

In this tutorial, we will be creating an application that utilizes Laravel queues to send our users “Happy Birthday” messages on their birthday. We will create a command that fetches all users whose birthdays are today, and sends them to the queue to be processed.

Pre-requisites

Setting Up our Application

As our application is Laravel-based, we will create a new Laravel application. If you're not familiar with the Laravel framework, here is a solid guide to help you get started. Create a new application with the laravel new command and assign the folder name.

$ laravel new birthday-reminder
$ cd birthday-reminder

Next, Update the database-related configurations in your project's .env file. If you are unfamiliar with dotenv, you can learn more about how this configuration safely protects your credentials from public view.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=birthday_reminder
DB_USERNAME=your_db_username
DB_PASSWORD=your_db_password

This assumes that you already have a database named birthday_reminder. If you don't, feel free to create it at this point.

Now, open up the user migrations file located at YOUR-APP-NAME/database/migrations/2014_10_12_000000_create_users_table.php and update the up() method to this:

public function up()
{
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('phone')->unique();
            $table->date('date_of_birth');
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
}

This method will generate the users table with the phone and date_of_birth columns.

Next we will modify the $fillable attribute of our User model class to reflect the changes in our users table structure. The User Model can be found in the file app/User.php.

  protected $fillable = [
        'name', 'email', 'phone', 'date_of_birth', 'password',
    ];

Seeding the Database

In real-life, our users would be providing us with the data at sign up or when they update their profile. To keep things simple, we will seed our database with sample data. Let's create the seeder file by running php artisan make:seeder UsersTableSeeder from our project root and add the following code to the newly created file database/seeds/UsersTableSeeder.php.

<?php

use Illuminate\Database\Seeder;
use App\User;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $users = [
            [
                'name' => "Oyewole Precious",
                'email' => "sansa@example.com",
                'phone' => "+234957000000",
                'date_of_birth' => "2000-09-29",
                'password' => "super_secret"
            ],
            [
                'name' => "Akande Salami",
                'email' => "cersei@example.com",
                'phone' => "+234956000001",
                'date_of_birth' => "1997-10-01",
                'password' => "super_secret"
            ]
            ];

        foreach ($users as $userData) {
            $user = new User($userData);
            $user->save();
        }
    }
}

NOTE: To see this work though, use real phone numbers and use the current date as the date_of_birth, so that our queue can process them instantly.

Next, open up database/seeds/DatabaseSeeder.php and paste in the following:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UsersTableSeeder::class);
    }
}

This will tell Laravel to run our UsersTableSeeder when we attempt to seed the database. Now, run the seeders with:

$ php artisan migrate
$ php artisan db:seed

Install the Twilio PHP SDK

The Twilio SMS API provides a robust interface for your application to programmatically use Twilio services. We will be using the Twilio PHP SDK to send the SMS. Import it via Composer with the following command.

$ composer require twilio/sdk

Next, add the variables below to your .env file.

TWILIO_ACCOUNT_SID="ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
TWILIO_AUTH_TOKEN="Your Auth Token"
TWILIO_PHONE_NUMBER="Your Twilio Phone Number in E.164 format (i.e. +13365555555)"

With that setup, let's make our Twilio configuration available globally. Open up config/services.php and add the following to the services array already present:

'twilio' => [
    'account_sid' => env('TWILIO_ACCOUNT_SID'),
    'auth_token'  => env('TWILIO_AUTH_TOKEN'),
    'phone_number' => env('TWILIO_PHONE_NUMBER')
]

Meet Laravel Jobs

In Laravel, queues are represented by Jobs. Generating a new queued job is accomplished  by using the Artisan command, similarly to generating controllers and models. Let's create a job:

$ php artisan make:job SendBirthdayMessage

By default, the above command would create a SendBirthdayMessage.php file in app/Jobs. The following code will represent our logic to send a birthday message:

<?php

namespace App\Jobs;

use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Client;

class SendBirthdayMessage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private $user;
    private $twilioClient;

    /**
     * Create a new job instance.
     * @param User $user: A single user instance whose birthday is current date.
     * @throws ConfigurationException
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
        $sid = config('services.twilio.account_sid');
        $token = config('services.twilio.auth_token');
        $this->twilioClient = new Client($sid, $token);
    }

    /**
     * Execute the job.
     * @throws TwilioException
     * @return void
     */
    public function handle()
    {
        $template = "You're finally old enough to know better! Happy Birthday %s";
        $body = sprintf($template, $this->user->name);
        $message = $this->twilioClient->messages->create(
            $this->user->phone,
            [
                'from' => config('twilio.phone_number'),
                'body' => $body
            ]
        );
        Log::info($message->sid);
    }
}

Now, we need a way to fetch all users whose birthdays are today, so we can dispatch the jobs that would send the message. To do that, we will use an Artisan command. It’s likely that you've used some of the built-in commands already (think php artisan db:seed, php artisan serve, etc). Here is some pretty in-depth documentation for it, should you desire to learn more. We’ll now create a custom one with the following command:

$ php artisan make:command BirthdaysReminderCommand

This command generates a BirthdaysReminderCommand class in app/Console/Commands. The signature attribute of the newly created class is what we call from the Artisan CLI, similar to db:seed and cache:clear. The description attribute is simply a basic summary of what our command does. Update both attributes like so:

<?php

/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'birthdays:send-wish';

/**
 * The console command description.
 *
 * @var string
 */
protected $description = 'Send out a birthday message to users whose birthdays are today.';

Change the handle() method to match the code below:

<?php

use Carbon\Carbon;
use App\User;
use App\Jobs\SendBirthdayMessage;

/**
 * Execute the console command.
 *
 * @return void
 */
public function handle()
{
    $today = Carbon::now()->format('Y-m-d');
    $users = User::where('date_of_birth', '=', $today)->get();
    if (!empty($users)) {
        foreach ($users as $user) {
            SendBirthdayMessage::dispatch($user);
        }
    }
}

In essence, what we are doing is fetching all the users whose birthdays are today and dispatching the BirthdayReminderJob which would in turn, send the SMS.

Configuring our Queue Driver

As mentioned earlier, Laravel supports different queue implementation such as Database, Redis, Beanstalk, etc. For this post though, we will be using the Database driver. All we need to do is set up the necessary database tables. This can be achieved by running the following commands:

$ php artisan queue:table
$ php artisan migrate

Now that the table is set up, let's start up our queue and listen for dispatched jobs.

$ php artisan queue:work --daemon

Testing

While our queue listener is still running, run the Artisan commands using the same signature we added to the BirthdaysReminderCommand:

$ php artisan birthdays:send-wish

If works as expected, the SMS should be sent to the matched user(s) and reflected on the Twilio dashboard as well.

Conclusion

We have a better grasp of using queues with the Laravel framework now. If you're deploying to production, you could set up a cron job to execute birthdays:send-wish every day or explore process monitors such as Supervisor.

In any case, feel free to reach out to me on Twitter @firechael if you have questions or suggestions.