Occasionally, your application needs to perform some routine tasks in timed intervals such as sending out weekly reports to an administrator or sending monthly updates to your users.
Traditionally, such routine tasks are carried out via individual cron jobs that trigger a shell script or the part of the code to be executed. As the application grows and the number of tasks we need to run increases, cron jobs quickly become difficult to maintain, especially as they don’t come with error reporting/handling features and they can’t be placed in a Version Control System such as Git.
The Laravel Task Scheduler allows us to define our tasks as code while leveraging all of the logging and error handling features available in the framework. With the scheduler, we only need to define a single cron entry that executes the schedule at an interval.
What are we Building?
In this tutorial, we will be building an application that uses SendGrid and the Laravel Task Scheduler to send daily reports of the number of new signups each day.
Pre-requisites
To complete this tutorial you will need:
- Basic understanding of the Laravel framework
- Composer installed
- MySQL (or your preferred database engine) installed
- A SendGrid account (Create a new account here)
Setting up our Sample Application
Create a new Laravel application using Composer and enter into the directory.
$ composer create-project --prefer-dist laravel/laravel daily-metrics
$ cd daily-metrics
Next, open your favorite IDE and update the database-related variables in your .env
file with your setup configuration.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=daily_metrics
DB_USERNAME=root
DB_PASSWORD=
This assumes that there is an existing database named daily_metrics
. If none exists, you can create it at this point.
By default, Laravel comes with an auto-generated users migration file at database/migrations/2014_10_12_00000_create_users_table.php
. This would be the only migration file we need as it contains all that we need for this application. Run php artisan migrate
in the project root to apply the schema defined in the migration file to your database. It will generate the users table with the columns defined in the up
method of the file.
Add the Laravel Authentication
Now that the users table has been created, we need a way to register users. Laravel 6+ has decoupled authentication by creating a package to scaffold all of the routes and views needed for registration. In the project directory, run the following commands:
$ composer require laravel/ui
$ php artisan ui vue --auth
After successful installation, launch the Laravel application by running php artisan serve
and navigate to http://127.0.0.1:8000/
to register a user. We will need to have a user added to the database in order to test the application at the end of the tutorial.
Installing and Setting Up the SendGrid Library
The SendGrid SDK helps us to easily use the SendGrid API in our PHP applications, thus, we would be using it in place of the built-in Laravel Mail. Install the library with the command below:
$ composer require sendgrid/sendgrid
Next, retrieve your SendGrid API key from your SendGrid API Keys Settings and append it to your .env
file, alongside your preferred recipient email and name as shown:
SENDGRID_API_KEY="YOUR_SENDGRID_API_KEY"
ADMIN_EMAIL="YOUR_EMAIL"
ADMIN_NAME="Daily Metrics"
Creating our Recurring Task
Tasks for the Scheduler can be defined in various forms including Closures, Queued Jobs, Artisan commands, and PHP invokable objects. In this case, we would be using an invokable object as it provides enough flexibility for us to achieve what we plan to do.
Create a new Tasks
directory in your app
folder i.e, in the same directory as Console
and Http
and add the SendDailyMetrics.php
file in the new folder by running the commands below in your terminal.
$ mkdir -p app/Tasks
$ touch app/Tasks/SendDailyMetrics.php
In your editor, open the SendDailyMetrics.php
file and add the code block below to it. Comments have been added to aid inclarity.
<?php
namespace App\Tasks;
use App\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use SendGrid\Mail\From;
use SendGrid\Mail\Mail;
use SendGrid\Mail\To;
class SendDailyMetrics
{
public function __invoke()
{
// retrieve user count as at previous day
$yesterday = Carbon::yesterday()->format('Y-m-d');
$usersFromYesterday = User::whereDate('created_at', $yesterday)->count();
// retrieve user count as at today
$today = Carbon::today()->format('Y-m-d');
$usersToday = User::whereDate('created_at', $today)->count();
// calculate the percentage difference
if ($usersFromYesterday > 0) {
$percentageDiff = ((abs($usersFromYesterday - $usersToday)) / $usersFromYesterday) * 100;
if ($usersFromYesterday > $usersToday) {
// convert to it's negative equivalent if the number of users declined
$percentageDiff = -1 * $percentageDiff;
}
$this->sendEmail($usersToday, $percentageDiff);
}
$this->sendEmail($usersToday);
}
private function sendEmail(int $userCount, $percentageDiff = 0) {
// only append the '%' sign if an actual percentage was provided.
$percentageDiff = ($percentageDiff == 0) ? $userCount : $percentageDiff."%";
$name = getenv('ADMIN_NAME');
$htmlContent = "<p>Hi $name, We got <b>$userCount</b> new users today,
a <b>$percentageDiff</b> difference from yesterday</p>";
$textContent = "Hi $name, We got $userCount new users today,
a $percentageDiff difference from yesterday</p>";
$from = new From(getenv('ADMIN_EMAIL'), getenv('ADMIN_NAME'));
$subject = "Here's how our app performed today.";
$recipient = new To(getenv('ADMIN_EMAIL'), getenv('ADMIN_NAME'));
$email = new Mail();
$email->setFrom($from);
$email->setSubject($subject);
$email->addTo($recipient);
$email->addContent("text/plain", $textContent);
$email->addContent("text/html", $htmlContent);
$sendgrid = new \SendGrid(getenv('SENDGRID_API_KEY'));
try {
$response = $sendgrid->send($email);
$context = json_decode($response->body());
if ($response->statusCode() == 202) {
Log::info("Metric email has been sent", ["context" => $context]);
}else {
Log::error("Failed to send metric email", ["context" => $context]);
}
} catch (\Exception $e) {
Log::error($e);
}
}
}
The __invoke
magic method above is what makes instances of the SendDailyMetrics
class invocable objects. They are executed when such instances are called as functions (which is how the Laravel Task Scheduler invokes them).
When the __invoke
method is called, we first retrieve the number of users that registered the previous day and the number that registered on the current day. Also, we calculate the percentage based on these two numbers which are then sent to the admin email as the day’s metric.
Next, we will update the schedule
method of our Console’s Kernel class to execute the task everyday at 11:59 PM, and send whatever output it has to a log file we have explicitly specified. That way, Laravel is also aware of our new Task. Open the Kernel.php
file in the app/Console/
directory and replace it’s schedule
method with the code below:
protected function schedule(Schedule $schedule)
{
$tasksLog = storage_path('/logs/tasks-output.log');
$schedule->call(new SendDailyMetrics)
->daily()
->at("23:59")
->appendOutputTo($tasksLog);
}
NOTE: Remember to import the SendDailyMetrics
class.
Testing our Application
Our application requires only the cron entry below to execute any number of tasks that are available. It will execute the scheduler every minute, which will in turn, go through the scheduled tasks and execute the ones that are due
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
NOTE: If you are unfamiliar with adding cron entries, the cron man page could give you a headstart or you can consider using tools like Laravel Forge.
While developing locally, you can lower the schedule frequency to a minute and directly execute php artisan schedule:run
to get the email immediately.
Conclusion
Recurring tasks are a frequently needed feature of modern applications and this tutorial provides a guide on implementing such features with Laravel, while also going through how to send emails with the Twilio SendGrid SDK.
Michael Okoko is a student currently pursuing a degree in Computer Science and Mathematics at Obafemi Awolowo University, Nigeria. He loves open source and is mostly interested in Linux, Golang, PHP, and fantasy novels! You can reach him via: