Tiếp tục với series, bài này mình sẽ giới thiệu với mọi người về mail và notification trong Laravel 13: gửi mail chào mừng khi đăng ký và báo cho người dùng khi họ được giao task.
1. Cấu hình mail.
Laravel gửi mail qua các mailer trong config/mail.php (smtp, ses, log...), chọn bằng biến MAIL_MAILER trong .env. Mặc định là log: mail không gửi đi mà ghi vào storage/logs/laravel.log, tiện khi chạy local. Mình chỉ sửa địa chỉ người gửi thành MAIL_FROM_ADDRESS="[email protected]".
Muốn xem mail như hộp thư thật thì chạy Mailpit (docker run -p 8025:8025 -p 1025:1025 axllent/mailpit), đặt MAIL_MAILER=smtp, MAIL_PORT=1025, giao diện ở cổng 8025.
2. Tạo Mailable với Markdown.
Mỗi loại mail là một class Mailable trong app/Mail. --markdown tạo kèm template Markdown, Laravel tự render ra HTML và bản text:
php artisan make:mail WelcomeMail --markdown=mail.welcome
# INFO Mailable [app/Mail/WelcomeMail.php] created successfully.
# INFO Markdown view [resources/views/mail/welcome.blade.php] created successfully.
- app/Mail/WelcomeMail.php
<?php
namespace App\Mail;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class WelcomeMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public User $user,
) {}
public function envelope(): Envelope
{
return new Envelope(
subject: 'Chào mừng bạn đến với TaskFlow',
);
}
public function content(): Content
{
return new Content(
markdown: 'mail.welcome',
with: ['url' => route('projects.create')],
);
}
public function attachments(): array
{
return [];
}
}
Trong đó:
envelope():subject,from,replyTo...content(): template (markdownhoặcview),withlà dữ liệu thêm; property public như$usercó sẵn trong template.attachments(): file đính kèm, VDAttachment::fromStorage('guides/huong-dan.pdf')->as('huong-dan.pdf')lấy file trên disk (Bài 20).
Template dùng các component x-mail::, không thụt đầu dòng vì Markdown coi dòng thụt vào là code:
- resources/views/mail/welcome.blade.php
<x-mail::message>
# Chào {{ $user->name }}!
Tài khoản TaskFlow của bạn đã được tạo với email **{{ $user->email }}**.
Hãy tạo project đầu tiên để bắt đầu quản lý công việc.
<x-mail::button :url="$url">
Tạo project
</x-mail::button>
Trân trọng,<br>
{{ config('app.name') }}
</x-mail::message>
3. Gửi mail và xem trước.
Gửi bằng facade Mail, to() nhận email hoặc user (nối thêm được cc(), bcc()). Mình gửi ngay sau khi tạo user ở Bài 21:
- app/Http/Controllers/RegisterController.php (trích, thêm use App\Mail\WelcomeMail;, use Illuminate\Support\Facades\Mail;)
// gửi ngay trong request, Bài 28 sẽ đưa vào queue
Mail::to($user)->send(new WelcomeMail($user));
Đăng ký thử một tài khoản, cuối laravel.log sẽ có cả mail (dòng local.DEBUG: From: TaskFlow...). Route trả về một mailable thì Laravel render nó ra trình duyệt mà không gửi, tiện khi chỉnh giao diện. Mình chỉ bật nó khi chạy local:
- routes/web.php (trích, thêm use App\Mail\WelcomeMail;, use App\Models\User;)
if (app()->isLocal()) {
Route::get('/mail-preview/welcome', fn () => new WelcomeMail(User::firstOrFail()));
}
4. Notification.
Notification là thông báo ngắn về một việc vừa xảy ra, gửi được qua nhiều kênh (channel): mail, database, broadcast, vonage (SMS), slack. Mình tạo nó bằng php artisan make:notification TaskAssigned:
- app/Notifications/TaskAssigned.php
<?php
namespace App\Notifications;
use App\Models\Task;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class TaskAssigned extends Notification
{
use Queueable;
public function __construct(
public Task $task,
) {}
public function via(object $notifiable): array
{
return ['mail', 'database'];
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Bạn được giao task mới')
->greeting('Chào '.$notifiable->name.'!')
->line('Bạn vừa được giao task "'.$this->task->title.'".')
->action('Xem task', route('tasks.attachments.index', $this->task));
}
public function toArray(object $notifiable): array
{
return [
'task_id' => $this->task->id,
'title' => $this->task->title,
];
}
}
Trong đó:
via(): các kênh sẽ gửi,$notifiablelà người nhận.toMail(): dựng mail bằngMailMessage(greeting,line,actionlà nút bấm), không cần template. Nút trỏ tới trang file của task, người được giao xem được (Bài 22).toArray(): lưu thành JSON ở cộtdatacủa kênhdatabase.
Vài câu tiếng Anh trong template của MailMessage mình dịch ở lang/vi.json (VD "Regards,": "Trân trọng,"). Kênh database cần bảng notifications:
php artisan make:notifications-table
php artisan migrate
Cột read_at bằng null nghĩa là chưa đọc. User có sẵn trait Notifiable với method notify() (nhiều người thì Notification::send($users, ...)). Mình gửi trong TaskObserver (Bài 26) khi tạo task có người được giao hoặc đổi assignee_id:
- app/Observers/TaskObserver.php (trích, thêm use App\Notifications\TaskAssigned;)
public function created(Task $task): void
{
$this->notifyAssignee($task);
}
// cuối updated()
if ($task->wasChanged('assignee_id')) {
$this->notifyAssignee($task);
}
// load() lấy lại assignee mới nhất từ database
protected function notifyAssignee(Task $task): void
{
$task->load('assignee')->assignee?->notify(new TaskAssigned($task));
}
5. Đọc và đánh dấu đã đọc.
Chạy composer dump-autoload (Bài 15), php artisan migrate:fresh --seed, rồi vào php artisan tinker giao task 3 cho Bình:
Task::find(3)->update(['assignee_id' => 2]); // gửi mail + lưu vào bảng notifications
$binh = User::find(2);
$binh->unreadNotifications()->count(); // 1
$notification = $binh->notifications()->first();
$notification->data; // ["task_id" => 3, "title" => "Thiết kế trang chủ"]
$notification->markAsRead();
$binh->unreadNotifications()->count(); // 0
Ngoài ra còn readNotifications, và $user->unreadNotifications->markAsRead() đánh dấu tất cả đã đọc. Giờ làm trang thông báo (php artisan make:controller NotificationController):
- app/Http/Controllers/NotificationController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class NotificationController extends Controller
{
public function index(Request $request): View
{
$notifications = $request->user()->notifications()->paginate(10);
return view('notifications.index', compact('notifications'));
}
public function read(Request $request, string $id): RedirectResponse
{
// chỉ tìm trong thông báo của user đang đăng nhập
$notification = $request->user()->notifications()->findOrFail($id);
$notification->markAsRead();
return to_route('tasks.attachments.index', $notification->data['task_id']);
}
}
- routes/web.php (trích, thêm vào group auth của attachments và use App\Http\Controllers\NotificationController;)
Route::get('/notifications', [NotificationController::class, 'index'])->name('notifications.index');
Route::post('/notifications/{id}/read', [NotificationController::class, 'read'])->name('notifications.read');
- resources/views/notifications/index.blade.php
<x-layout title="Thông báo">
<h1 class="mb-4 text-2xl font-bold">Thông báo</h1>
@forelse ($notifications as $notification)
<form method="POST" action="{{ route('notifications.read', $notification->id) }}" class="mb-2">
@csrf
<button type="submit" @class(['text-blue-600', 'font-bold' => $notification->unread()])>
Bạn được giao task "{{ $notification->data['title'] }}"
</button>
{{ $notification->created_at->diffForHumans() }}
</form>
@empty
<x-alert>Chưa có thông báo nào.</x-alert>
@endforelse
{{ $notifications->links() }}
</x-layout>
Thanh menu (partials/nav.blade.php) thêm link tới trang này kèm số chưa đọc auth()->user()->unreadNotifications()->count(). Giao thêm task 7 cho Bình rồi đăng nhập bằng [email protected] sẽ thấy "Thông báo (1)", bấm vào thông báo in đậm thì sang /tasks/7/attachments và số đếm về 0.
6. Lời kết.
Như vậy chúng ta đã tìm hiểu xong mail và notification trong Laravel 13: Mailable với Markdown, gửi, xem trước mail và notification qua kênh mail, database. Xem thêm tại tài liệu chính thức. Mail đang gửi ngay trong request nên sẽ chậm với SMTP thật, bài sau mình sẽ giới thiệu về queue và job trong Laravel 13 để đẩy việc này chạy nền.
Đăng ký nhận tin.
Chúng tôi chỉ gửi tối đa 2 lần trên 1 tháng. Tuyên bố không spam mail!





0 Comments