Notificaciones en Laravel 13: un solo sistema para email, base de datos y Slack
Una sola clase de notificación con un método via() puede enviar el email de confirmación de un pedido, guardar el aviso en la base de datos para la campanita de la app y avisar al equipo por Slack. Así funciona el sistema de notificaciones de Laravel 13, y esta guía lo monta de cero sobre un caso real de una tienda.
Qué son las notificaciones en Laravel 13
El sistema de notificaciones de Laravel 13 es una capa central que separa el acto de notificar (un evento del negocio: se confirmó un pedido) de los canales de entrega (email, base de datos, Slack, SMS, broadcast). En vez de escribir tres bloques de lógica distintos cada vez que algo ocurre, defines una clase de notificación por evento y Laravel se encarga de llevarla a cada canal.
Una clase, varios canales: el método via()
Cada notificación es una clase PHP que declara un método via(). Ese método devuelve un array con los canales por los que debe entregarse el mensaje: mail, database, broadcast, slack, vonage o cualquier canal de la comunidad. Después, para cada canal que declares, la clase define un método que construye el mensaje: toMail() devuelve un MailMessage, toDatabase() un array plano, toSlack() un mensaje de Slack. Añadir un canal nuevo no toca la lógica de negocio: solo se agrega un método.
Notifiable: qué modelos pueden recibir notificaciones
Para que un modelo pueda recibir notificaciones debe usar el trait Notifiable. El modelo User de los skeletons de Laravel ya lo incluye, pero puedes añadirlo a cualquier otro modelo: pedidos, clientes, invitados de un evento. El trait aporta el método notify() y el acceso a la relación de notificaciones guardadas en base de datos.
Tu primera notificación: confirmación de pedido por email
El ejemplo conductor de esta guía es una tienda: cuando un cliente paga un pedido, queremos mandarle el email de confirmación, guardar un aviso en su bandeja interna y alertar al equipo por Slack. Empieza por el canal que todos esperan, el email.
php artisan make:notification y la estructura de una notificación
El comando php artisan make:notification OrderConfirmed genera la clase base en app/Notifications. La estructura mínima es el método via() y un método por canal. La notificación suele recibir los datos que necesita en el constructor, como la instancia del pedido:
class OrderConfirmed extends Notification
{
public function __construct(public Order $order) {}
public function via(object $notifiable): array
{
return ['mail', 'database', 'slack'];
}
}La plantilla Blade del email con toMail()
El método toMail() construye un MailMessage, que encadena asunto, líneas de texto y acciones. La salida se renderiza con las plantillas Blade que Laravel incluye por defecto y hereda el tema de correos de la aplicación:
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Pedido ' . $this->order->number . ' confirmado')
->greeting('Hola, ' . $notifiable->name)
->line('Tu pedido se ha confirmado y está en preparación.')
->action('Ver mi pedido', url('/pedidos/' . $this->order->id))
->line('Gracias por comprar en nuestra tienda.');
}Enviar con notify() y Notification::send()
Cuando el pago se procesa, se dispara la entrega. Desde el modelo receptor, $user->notify(new OrderConfirmed($order)); cuando son varios destinatarios, Notification::send($users, new OrderConfirmed($order)) recorre la colección y entrega a cada uno. Ambos caminos usan el mismo mecanismo interno, así que puedes elegir el que lea mejor en cada punto del código.
El canal database: avisos dentro de la aplicación
El canal database guarda la notificación en una tabla y la app la muestra en una campanita o panel de avisos. Es útil para cosas que el usuario debe ver al volver, no en su correo: cambios de estado, mensajes de soporte, resultados de una acción asíncrona.
La tabla notifications y su migración por defecto
Desde Laravel 11, la migración create_notifications_table viene incluida en el skeleton y se ejecuta con las migraciones habituales. Si tu proyecto se creó antes o la tabla no está, el comando php artisan make:notifications-table la genera. La tabla guarda el tipo de notificación, los datos en JSON y las marcas de lectura.
Marcar como leídas y consultar las no leídas
El trait Notifiable expone unreadNotifications y readNotifications como relaciones. Para pintar el contador de la campanita basta $user->unreadNotifications->count(), y al abrir el panel se marcan como leídas con $notification->markAsRead(). El array devuelto por toDatabase() queda disponible como $notification->data en la vista.
Slack y el resto de canales: broadcast, Vonage y comunidad
Fuera de los canales del núcleo, Laravel integra servicios de terceros mediante paquetes que respetan el mismo contrato: un método via() que lista el canal y un método toXxx() que construye el mensaje.
Slack con laravel/slack-notification-channel
El paquete oficial laravel/slack-notification-channel se instala con Composer y se configura apuntando a un webhook entrante de Slack. En la notificación, toSlack() devuelve un SlackMessage con texto, adjuntos o botones. El equipo recibe la alerta del pedido en el canal configurado sin necesidad de tocar la lógica de la tienda.
Cuándo usar broadcast (Reverb) y SMS (Vonage)
El canal broadcast publica la notificación en tiempo real por websockets (Reverb en el stack actual de Laravel) para mostrarla al instante en la interfaz. vonage envía SMS, útil para confirmaciones urgentes o clientes sin app. La regla práctica: email y database para lo que puede esperar, broadcast para lo inmediato y SMS solo cuando el teléfono es el único canal fiable. La comunidad añade más canales, como Telegram, con el mismo patrón.
Notificaciones on-demand para usuarios sin modelo
A veces el destinatario no tiene cuenta: un cliente que compra como invitado o una dirección de correo recogida en un formulario. Las notificaciones on-demand lo resuelven sin crear modelos: Notification::route('mail', 'invitado@example.com')->notify(new OrderConfirmed($order)). El método route() define el canal y el destino, y puedes encadenar varias rutas para mandar por mail y por SMS al mismo tiempo.
Colas: que el envío no bloquee la respuesta
Enviar un email o llamar a un webhook de Slack dentro del ciclo de una petición HTTP añade latencia y un punto de fallo. La solución es encolar la notificación para que un worker la procese en segundo plano.
ShouldQueue y el flujo con workers
Haciendo que la clase implemente ShouldQueue, Laravel coloca la notificación en la cola por defecto y el envío ocurre cuando un worker la recoge, sin que el cliente espere. El resto de la clase no cambia: los métodos toXxx() se ejecutan en el worker, así que conviene pasar al constructor los datos que se necesiten, no objetos que puedan quedar obsoletos.
Retrasar envíos con ->delay() y el atributo #[Delay] de Laravel 13.4+
Para enviar más tarde, el encadenado clásico es ->delay(now()->addMinutes(10)). Desde Laravel 13.4, el atributo #[Delay] generaliza este retraso: se declara sobre la clase o el constructor y Laravel lo respeta tanto en jobs como en notificaciones, lo que evita esparcir llamadas a delay() por el código. Es la opción natural para recordatorios o mensajes que deben esperar a que el pedido se confirme.
Evitar duplicados y limitar el ritmo de envío
Dos protecciones evitan que el sistema se dispare: una contra duplicados y otra contra ráfagas de envíos.
ShouldBeUnique y throttling de notificaciones
Si un evento puede dispararse varias veces (un webhook de pago repetido), la notificación implementa ShouldBeUnique y Laravel no encola una segunda copia mientras la primera siga en cola o en proceso. Para limitar el ritmo por canal y destinatario, el throttling usa un cache store y define cuántos envíos se permiten por ventana de tiempo, útil en campañas o avisos masivos.
Testing con Notification::fake()
Los tests no deben tocar el driver real. Con Notification::fake() se sustituye el envío y se verifican las aserciones sin mandar nada:
Notification::fake();
$user->notify(new OrderConfirmed($order));
Notification::assertSentTo($user, OrderConfirmed::class);
Notification::assertNotSentTo($otherUser, OrderConfirmed::class);Además de assertSentTo, el fake expone assertSentOnDemand para notificaciones on-demand y assertNothingSent para casos negativos, lo que permite cubrir el flujo completo de confirmación de pedido con confianza.
Conclusión
El sistema de notificaciones de Laravel 13 convierte un problema transversal (avisar por varios canales) en una clase por evento con métodos claros por canal. Empezando por el email de confirmación, añadir la campanita de base de datos, Slack para el equipo, on-demand para invitados y colas para no bloquear la respuesta cubre el 90% de las necesidades reales de una aplicación. Si quieres profundizar en el transporte de fondo de estos envíos, en este blog ya tienes guías sobre colas y jobs con Horizon y sobre Reverb para tiempo real.