Laravel framework has great notification system that can be used to send emails, SMS messages or other types of notifications to users. But there’s other popular way to notify users – daily digest about the events from that day. In this article, I will show you a demo-project with both of those.

The code of full project is available on Github, link is at the end of the article.


Demo-Project: Subscribe to Job Posts by Tags

Pretty realistic scenario – a job board project where potential employees/freelancers can subscribe for new jobs by their industry tags, and would get notified by email.

To make notifications personal, we added two new fields to typical Laravel register form:

Laravel register notifications

In terms of database, there are two new structures on top of default Laravel:

  • Column users.notifications_frequency (varchar with values “immediately” or “once”)
  • Table skills and pivot table skill_user with columns skill_id and user_id

Immediate Notification: When Admin Posts New Job

Here’s a form where administrator can post new job ads:

When that form is filled, Controller saves the data – here’s app/Http/Controllers/Admin/JobsController.php:

public function store(StoreJobRequest $request)
{
    $job = Job::create($request->all());

    return redirect()->route('admin.jobs.index');
}

Important for us is “Skills” field – that will determine, who would get notified.

Notifications are fired with Eloquent Observer class – app/Observers/JobObserver.php:

use App\Job;
use App\Notifications\NewJobImmediateNotification;
use App\User;
use Notification;

class JobObserver
{

    public function created(Job $job)
    {
        $job->skills()->sync(request()->input('skills', []));
        $skills = $job->skills->pluck('id');
        $users = User::where('notifications_frequency', 'immediately')
            ->whereHas('skills', function ($query) use ($skills) {
                $query->whereIn('id', $skills);
            })->get();

        Notification::send($users, new NewJobImmediateNotification($job));
    }
}

You can read more about Eloquent Observers here in the official documentation, basically it’s a simple class that is firing its methods automatically whenever Eloquent Model is created/updated/deleted etc.

To register observer with particular Model, we need to add this piece of code in app/Job.php:

use App\Observers\JobObserver;

class Job extends Model
{
    public static function boot()
    {
        parent::boot();
        self::observe(new JobObserver);
    }
}

Now, what is happening inside of that JobObserver method created()? Let’s repeat:

$job->skills()->sync(request()->input('skills', []));
$skills = $job->skills->pluck('id');
$users = User::where('notifications_frequency', 'immediately')
    ->whereHas('skills', function ($query) use ($skills) {
        $query->whereIn('id', $skills);
    })->get();

Notification::send($users, new NewJobImmediateNotification($job));

So:

  1. Job skills are saved in pivot table;
  2. We get the IDs of those skills and query users who subscribed to those tags with “immediate” notifications;
  3. We send notifications to all of them.

Now, what’s inside of app/Notifications/NewJobImmediateNotification.php?

class NewJobImmediateNotification extends Notification
{

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

    public function toMail($notifiable)
    {
        return (new MailMessage)
                    ->subject('New job has been posted')
                    ->line('A new job listing has been posted: '.$this->job->title)
                    ->line('Skills: '.$this->job->skills->implode('name', ', '))
                    ->line('Description: '.$this->job->description)
                    ->line('Contact email: '.$this->job->contact_email)
                    ->action('View job', route('admin.jobs.show', $this->job->id))
                    ->line('Thank you for using our application!');
    }
}

As you can see, we’re passing $job as a parameter to this class, so we need to transform that into private variable in __construct() method. And then, it’s all just forming the notification email line by line.

Here’s the email that is received:

Laravel email notification


Daily Digest: New Jobs of the Day

As I mentioned, it’s pretty popular to send daily/weekly digest emails with new events of the system, partly to save users from “spam” of emails every time.

To achieve that, we create Artisan command that will be called like this: php artisan jobs:digest.

Here’s our app/Console/Commands/SendDailyJobsEmails.php:

use App\Job;
use App\Notifications\NewJobsDailyNotification;
use App\User;
use Illuminate\Console\Command;

class SendDailyJobsEmails extends Command
{

    protected $signature = 'jobs:digest';

    protected $description = 'Send daily emails to users with list of new jobs';

    public function handle()
    {
        $jobs = Job::with('skills')
            ->whereHas('skills')
            ->whereBetween('created_at', [now()->subDay(), now()])
            ->get();

        $users = User::with('skills')
            ->where('notifications_frequency', 'once')
            ->get();

        foreach($users as $user)
        {
            $userSkills = $user->skills;
            $userJobs = $jobs->filter(function ($job) use ($userSkills) {
                return $job->skills->contains(function($key, $value) use($userSkills) {
                    return $userSkills->contains($value);
                });
            });

            if($userJobs->count())
            {
                $user->notify(new NewJobsDailyNotification($userJobs));
            }
        }     
    }
}

The code is self-explanatory, with some Collection filtering used.

Let’s take a look at app/Notifications/NewJobsDailyNotification.php:

class NewJobsDailyNotification extends Notification
{
    public function __construct($jobs)
    {
        $this->jobs = $jobs;
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)->markdown('mail.jobs', ['jobs' => $this->jobs]);
    }
}

Structure of this notification is a bit more complicated, so it was worth to separate it in separate template. Here’s resources/views/mail/jobs.blade.php:

@component('mail::message')
# Hello
There are new jobs for you
@foreach($jobs as $job) Title: {{ $job->title }}
Skills: {{ $job->skills->implode('name', ', ') }}
Description: {{ $job->description }}
Contact email: {{ $job->contact_email }}
@if(!$loop->last) * * * @endif @endforeach @component('mail::button', ['url' => route('admin.jobs.index')]) View Jobs @endcomponent Thanks,
{{ config('app.name') }} @endcomponent

You can read about Markdown syntax here, also read my article Laravel Mail Notifications: How to Customize the Templates.

Final thing – we need to schedule this daily command, we do it in app/Console/Kernel.php:

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('jobs:digest')
                 ->dailyAt('20:00');
    }
}

And that’s it, every evening users would get email like this:

As promised, Github repository with all the project: LaravelDaily/Laravel-Notifications-Daily-Immediate