Laravel
Mail & Notifications
Send emails using Laravel's built-in Mail system with SMTP configuration, Mailable classes, and Blade templates. Also covers Database notifications, queued mail, and the Notification class with toMail() and toDatabase() channels.
Mail Config in .env: Set your SMTP credentials. Use Mailtrap for development or configure Gmail, Mailgun, SES for production.
.env
ENV
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_mailtrap_user
MAIL_PASSWORD=your_mailtrap_pass
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="no-reply@myapp.com"
MAIL_FROM_NAME="${APP_NAME}"
Create a Mailable Class: Use Artisan to generate a Mailable. The class holds all the data and logic for building the email.
terminal
BASH
php artisan make:mail OrderConfirmationMail --markdown=emails.order-confirmation
app/Mail/OrderConfirmationMail.php
PHP
<?php
namespace App\Mail;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class OrderConfirmationMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(public Order $order)
{
//
}
public function envelope(): Envelope
{
return new Envelope(
subject: "Order Confirmed – #{$this->order->id}",
);
}
public function content(): Content
{
return new Content(
markdown: 'emails.order-confirmation',
with: [
'order' => $this->order,
'customer' => $this->order->user,
],
);
}
}
Mail Blade Template (Markdown): Laravel's markdown mail components provide a clean, responsive layout out of the box.
resources/views/emails/order-confirmation.blade.php
HTML
<x-mail::message>
# Order Confirmed!
Hi {{ $customer->name }},
Thank you for your order. Here's a summary:
<x-mail::table>
| Product | Qty | Price |
|:--------|:---:|------:|
@foreach($order->items as $item)
| {{ $item->product->name }} | {{ $item->qty }} | Rp {{ number_format($item->price, 0, ',', '.') }} |
@endforeach
</x-mail::table>
**Total: Rp {{ number_format($order->total_price, 0, ',', '.') }}**
<x-mail::button :url="route('orders.show', $order->id)">
View Order
</x-mail::button>
Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
Send Mail in Controller: Use the
Mail facade with to() and send(). Pass the recipient user or email address directly.app/Http/Controllers/OrderController.php
PHP
<?php
namespace App\Http\Controllers;
use App\Mail\OrderConfirmationMail;
use App\Models\Order;
use Illuminate\Support\Facades\Mail;
class OrderController extends Controller
{
public function store(Request $request)
{
$order = Order::create([
'user_id' => auth()->id(),
'total_price' => $request->total,
'payment_status' => 'pending',
]);
// Attach items...
// Send confirmation email immediately
Mail::to($order->user->email)
->send(new OrderConfirmationMail($order));
return redirect()->route('orders.show', $order)->with('success', 'Order placed!');
}
}
Queue Mail for Async Sending: Replace
send() with queue() to dispatch the mail to a background queue. Make sure your queue worker is running.app/Http/Controllers/OrderController.php
PHP
// Queue the email (non-blocking)
Mail::to($order->user->email)
->queue(new OrderConfirmationMail($order));
// Or delay by 5 minutes
Mail::to($order->user->email)
->later(now()->addMinutes(5), new OrderConfirmationMail($order));
// In .env, set the queue connection:
// QUEUE_CONNECTION=database
// Run the queue worker:
// php artisan queue:work
Create a Notification Class: Notifications can be sent via multiple channels simultaneously. Generate one with Artisan.
terminal
BASH
php artisan make:notification OrderShippedNotification
# Create notifications table for database channel
php artisan notifications:table
php artisan migrate
Notification Class with toMail & toDatabase: Implement both channels in a single Notification class.
via() determines which channels to use.app/Notifications/OrderShippedNotification.php
PHP
<?php
namespace App\Notifications;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderShippedNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(public Order $order)
{
//
}
// Define which channels to use
public function via(object $notifiable): array
{
return ['mail', 'database'];
}
// Email channel
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject("Your Order #{$this->order->id} Has Shipped!")
->greeting("Hello, {$notifiable->name}!")
->line("Great news! Your order has been shipped and is on its way.")
->line("Estimated delivery: 2–3 business days.")
->action('Track Order', route('orders.show', $this->order->id))
->line('Thank you for shopping with us!');
}
// Database channel – stored in notifications table
public function toDatabase(object $notifiable): array
{
return [
'order_id' => $this->order->id,
'message' => "Your order #{$this->order->id} has been shipped.",
'action_url' => route('orders.show', $this->order->id),
];
}
}
Send a Notification: Call
notify() on a notifiable model (User), or use the Notification facade to send to multiple users at once.app/Http/Controllers/ShipmentController.php
PHP
use App\Notifications\OrderShippedNotification;
use Illuminate\Support\Facades\Notification;
use App\Models\Order;
use App\Models\User;
// Send to a single user
$order = Order::findOrFail($id);
$order->user->notify(new OrderShippedNotification($order));
// Send to multiple users at once (e.g., admins)
$admins = User::where('role', 'admin')->get();
Notification::send($admins, new OrderShippedNotification($order));
Read Database Notifications: Access unread notifications from the authenticated user's
notifications relationship and mark them as read.app/Http/Controllers/NotificationController.php
PHP
<?php
namespace App\Http\Controllers;
class NotificationController extends Controller
{
public function index()
{
$user = auth()->user();
// All notifications (paginated)
$notifications = $user->notifications()->paginate(15);
// Unread count for badge
$unreadCount = $user->unreadNotifications()->count();
return view('notifications.index', compact('notifications', 'unreadCount'));
}
public function markAsRead($id)
{
$notification = auth()->user()->notifications()->findOrFail($id);
$notification->markAsRead();
return back();
}
public function markAllRead()
{
auth()->user()->unreadNotifications->markAsRead();
return back()->with('success', 'All notifications marked as read.');
}
}