Notifications & Mail in Laravel
Laravel provides a powerful and flexible system for sending notifications across multiple channels including email, SMS, Slack, and database storage. The notification system makes it easy to notify users about events in your application.
Mail Configuration
Before sending mail, configure your mail driver in .env:
# Using SMTP
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME="${APP_NAME}"
# Using Mailgun
MAIL_MAILER=mailgun
MAILGUN_DOMAIN=your-domain.com
MAILGUN_SECRET=your-mailgun-key
# Using SendGrid
MAIL_MAILER=sendgrid
SENDGRID_API_KEY=your-sendgrid-key
Note: For Gmail, you need to use App Passwords (not your regular Gmail password) if 2FA is enabled. Generate one in your Google Account security settings.
Creating Mailables
Mailables represent email messages as PHP classes. Create a mailable using Artisan:
php artisan make:mail WelcomeEmail
php artisan make:mail OrderConfirmation --markdown=emails.orders.confirmation
Example mailable class:
<?php
namespace App\Mail;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class WelcomeEmail extends Mailable
{
use Queueable, SerializesModels;
public $user;
public $verificationUrl;
public function __construct(User $user, string $verificationUrl)
{
$this->user = $user;
$this->verificationUrl = $verificationUrl;
}
public function build()
{
return $this->subject('Welcome to ' . config('app.name'))
->view('emails.welcome')
->with([
'userName' => $this->user->name,
'url' => $this->verificationUrl,
]);
}
}
Sending Mail
Send mail using the Mail facade:
use App\Mail\WelcomeEmail;
use Illuminate\Support\Facades\Mail;
// Send immediately
Mail::to($user->email)->send(new WelcomeEmail($user, $verificationUrl));
// Send to multiple recipients
Mail::to($user)
->cc('manager@example.com')
->bcc('admin@example.com')
->send(new OrderConfirmation($order));
// Queue the email for background processing
Mail::to($user->email)->queue(new WelcomeEmail($user, $url));
// Queue with delay
Mail::to($user->email)
->later(now()->addMinutes(10), new WelcomeEmail($user, $url));
Markdown Mail
Laravel supports beautiful, responsive email templates using Markdown:
// Create markdown mailable
php artisan make:mail InvoicePaid --markdown=emails.invoice.paid
Mailable with markdown:
<?php
class InvoicePaid extends Mailable
{
public $invoice;
public function build()
{
return $this->markdown('emails.invoice.paid')
->subject('Invoice Paid - #' . $this->invoice->id);
}
}
Markdown template (resources/views/emails/invoice/paid.blade.php):
@component('mail::message')
# Invoice Paid
Thank you for your payment!
**Invoice:** #{{ $invoice->id }}
**Amount:** ${{ number_format($invoice->amount, 2) }}
@component('mail::button', ['url' => route('invoices.show', $invoice)])
View Invoice
@endcomponent
@component('mail::panel')
This invoice has been marked as paid on {{ $invoice->paid_at->format('M d, Y') }}.
@endcomponent
@component('mail::table')
| Item | Quantity | Price |
|:-----|:--------:|------:|
@foreach($invoice->items as $item)
| {{ $item->name }} | {{ $item->quantity }} | ${{ number_format($item->price, 2) }} |
@endforeach
@endcomponent
Thanks,<br>
{{ config('app.name') }}
@endcomponent
Tip: Customize the markdown email styling by publishing the mail assets: php artisan vendor:publish --tag=laravel-mail
Notifications System
Notifications are more versatile than mail, supporting multiple channels. Create a notification:
php artisan make:notification NewOrderNotification
php artisan make:notification PaymentReceived --markdown=notifications.payment-received
Example notification class:
<?php
namespace App\Notifications;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\BroadcastMessage;
class NewOrderNotification extends Notification
{
use Queueable;
public $order;
public function __construct(Order $order)
{
$this->order = $order;
}
// Specify which channels to use
public function via($notifiable)
{
return ['mail', 'database', 'broadcast'];
}
// Email notification
public function toMail($notifiable)
{
return (new MailMessage)
->subject('New Order #' . $this->order->id)
->line('You have received a new order.')
->line('Order Total: $' . number_format($this->order->total, 2))
->action('View Order', route('orders.show', $this->order))
->line('Thank you for using our application!');
}
// Database notification
public function toDatabase($notifiable)
{
return [
'order_id' => $this->order->id,
'order_total' => $this->order->total,
'customer_name' => $this->order->customer->name,
'message' => 'New order #' . $this->order->id,
];
}
// Broadcast notification (for real-time)
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'order_id' => $this->order->id,
'message' => 'New order received',
]);
}
// Array notification
public function toArray($notifiable)
{
return [
'order_id' => $this->order->id,
'order_total' => $this->order->total,
];
}
}
Sending Notifications
Send notifications using the Notifiable trait:
use App\Notifications\NewOrderNotification;
// Send to a single user (User model must use Notifiable trait)
$user->notify(new NewOrderNotification($order));
// Send to multiple users
$admins = User::where('role', 'admin')->get();
Notification::send($admins, new NewOrderNotification($order));
// Anonymous notifiable (send without user model)
Notification::route('mail', 'guest@example.com')
->route('slack', config('services.slack.webhook'))
->notify(new NewOrderNotification($order));
Database Notifications
Store notifications in database for in-app notification centers:
// Create notifications table
php artisan notifications:table
php artisan migrate
Retrieve and mark notifications as read:
// Get all notifications
$notifications = $user->notifications;
// Get unread notifications
$unread = $user->unreadNotifications;
// Mark as read
$user->unreadNotifications->markAsRead();
// Mark specific notification as read
$notification = $user->notifications->find($id);
$notification->markAsRead();
// Get notification data
foreach ($user->unreadNotifications as $notification) {
echo $notification->data['message'];
echo $notification->created_at->diffForHumans();
}
// Delete notification
$notification->delete();
Notification Channels
Laravel supports multiple notification channels out of the box:
// Install Slack notification channel
composer require laravel/slack-notification-channel
// In notification class
public function via($notifiable)
{
$channels = ['database'];
if ($notifiable->email_notifications) {
$channels[] = 'mail';
}
if ($notifiable->slack_webhook) {
$channels[] = 'slack';
}
return $channels;
}
// Slack notification
public function toSlack($notifiable)
{
return (new SlackMessage)
->from('Order Bot', ':shopping_cart:')
->to('#orders')
->content('New order received!')
->attachment(function ($attachment) {
$attachment->title('Order #' . $this->order->id)
->fields([
'Customer' => $this->order->customer->name,
'Total' => '$' . number_format($this->order->total, 2),
]);
});
}
Warning: Always queue notifications that send external requests (email, Slack, SMS) to avoid blocking your application. Use the ShouldQueue interface on notification classes.
Queueable Notifications
Make notifications queueable for better performance:
<?php
use Illuminate\Contracts\Queue\ShouldQueue;
class NewOrderNotification extends Notification implements ShouldQueue
{
use Queueable;
// Specify queue connection
public $connection = 'redis';
// Specify queue name
public $queue = 'notifications';
// Number of times to attempt
public $tries = 3;
// Timeout in seconds
public $timeout = 60;
}
Exercise 1: Create a "PasswordChanged" notification that sends via email and database. The email should use markdown and include a button to contact support if the user didn't make the change.
Exercise 2: Create a notification preference system where users can enable/disable email and database notifications. Modify the notification's via() method to respect these preferences.
Exercise 3: Build a notification center component that displays unread notifications with a badge count. Add functionality to mark notifications as read when clicked and delete them.
Testing Notifications
Test notifications using Laravel's built-in assertions:
use Illuminate\Support\Facades\Notification;
public function test_user_receives_welcome_email()
{
Notification::fake();
$user = User::factory()->create();
$user->notify(new WelcomeEmail($user, 'http://verify.url'));
Notification::assertSentTo($user, WelcomeEmail::class);
Notification::assertSentTo(
$user,
WelcomeEmail::class,
function ($notification, $channels) use ($user) {
return $notification->user->id === $user->id;
}
);
}
Notifications and mail provide a unified, channel-agnostic way to keep users informed. In the next lesson, we'll explore events and listeners for building decoupled, event-driven applications.