Appearance
Sending mail
Send mail with a notification. One notification can reach the user by mail, in the notification bell and over the partner webhook, and it respects the notification settings the user chose in their profile.
Use a mailable only when the recipient is a plain email address that belongs to no user, or when the mail needs its own HTML layout instead of the standard template.
Write the notification
Put the class in app/Notifications, in a subdirectory that matches the subject. Mark it final and queue it.
php
final class BulkReplaceSourceVideoCompletedNotification extends Notification implements ShouldQueue
{
use Queueable, RespectsUserPreferences, SerializesModels;
public function __construct(
private readonly Video $sourceVideo,
private readonly int $successCount,
) {
$this->onQueue(QueueName::mail());
}
public function via(mixed $notifiable): array
{
return $this->filterChannelsByUserPreferences(['database', 'mail'], $notifiable);
}
public static function notificationType(): UserNotificationType
{
return UserNotificationType::BULK_REPLACE_SOURCE_VIDEO_COMPLETED;
}
public function toMail(User $notifiable): MailMessage
{
return new MailMessage()
->subject(__('video.bulk_replace_source.notification.subject_with_failures', locale: $notifiable->locale))
->line(__('video.bulk_replace_source.notification.body_with_failures', locale: $notifiable->locale))
->action(
__('video.bulk_replace_source.notification.action', locale: $notifiable->locale),
route('dashboard.enrich', ['video' => $this->sourceVideo]),
);
}
}Reference: app/Notifications/Video/BulkReplaceSourceVideoCompletedNotification.php.
Queue the notification
Implement ShouldQueue and use Queueable. Sending mail calls an external service, so it must not block the request. Put it on the mail queue with $this->onQueue(QueueName::mail()) in the constructor.
Use SerializesModels when the constructor accepts a model. The queue then stores the key instead of the whole model, and the worker loads the record again.
Return a MailMessage
A MailMessage renders in the standard mail template, which we style once in resources/views/vendor/mail. Keep every notification on that template. If the mail needs a layout of its own, it is a mailable.
Two notifications from 2024 render their own Blade file with ->view(). They predate this protocol, so do not copy them.
Translate in the recipient's locale
Pass the recipient's locale to every translation call:
php
__('video.bulk_replace_source.notification.subject_with_failures', locale: $notifiable->locale)A queued notification runs in a worker, after the request that created it has finished. The application locale there is the default one, not the locale of the person who receives the mail.
Respect the user's preferences
A user turns each notification type on or off per channel in their profile. The notification must honour that.
- Use the
RespectsUserPreferencestrait. - Implement
notificationType()and return the matchingUserNotificationTypecase. - Pass the channels through
filterChannelsByUserPreferences()beforevia()returns them.
Add a case to App\Enums\User\UserNotificationType for a new type. The profile screen is built from this enum, so the case needs more than the line that declares it:
notificationClass()has nodefaultarm. Add the case there, or the match throws.canBeConfiguredInUserProfile(),supportsDatabase(),supportsEmail()andcategory()each have adefaultarm. Add the case when the default is wrong for this notification.label()anddescription()read the translation keysuser.settings.notifications.types.<value>anduser.settings.notifications.types.<value>_description. Add both.
Add database to via() when the user must also see the message in the notification bell, and implement toDatabase().
When the notifiable is not a user
filterChannelsByUserPreferences() needs a User to read preferences from. Add the ResolvesNotifiableUser trait when the notifiable is another model or an AnonymousNotifiable. It finds the User behind the notifiable, so the preference check still applies.
Mailables
A mailable is the exception. Use one when:
- The recipient is an email address with no user behind it, such as a viewer who filled in a form.
- The mail needs its own HTML, as the PDF export mails do.
Put the class in app/Mail, suffix it with Mail, and put its Blade view in resources/views/emails. Queue it in the same way as a notification: implement ShouldQueue, use Queueable and SerializesModels, and set $this->queue = QueueName::mail(). Send it with the Mail facade:
php
Mail::to($viewerEmail)->send(new PdfExportMail($subject, $body, $pdf));A mailable ignores the notification preferences. Use one only when a notification cannot do the job.