php elephant sticker
Photo by RealToughCandy.com on Pexels.com
目次

[Guía Práctica Completa] Infraestructura de Notificaciones y Email en Laravel — Mailable/Notifications, Entregabilidad, Cancelación de Suscripción, Webhooks, Plantillas Accesibles, Integración SMS/Push, Medición y Pruebas

Lo que aprenderás (Puntos clave)

  • Cómo elegir entre Mailables y Notifications, y cómo diseñar canales (Email/SMS/Slack/Push/Base de datos)
  • Cómo mejorar la entregabilidad con una configuración de dominio adecuada (SPF/DKIM/DMARC), envíos en cola, reintentos e idempotencia
  • Cómo crear emails HTML accesibles y versiones en texto plano, asunto/texto de vista previa, cancelación de suscripción y gestión de suscripciones
  • Cómo manejar rebotes/quejas mediante Webhooks de entrada, listas de supresión y visualización de métricas
  • Cómo manejar multiidioma/zonas horarias/personalización, notificaciones escalonadas con SMS/Push, y pruebas + integración con CI

Lectores objetivo (¿Quién se beneficia?)

  • Desarrolladores Laravel de nivel principiante a intermedio: quieren enviar notificaciones de forma segura, fiable y legible
  • Líderes técnicos en SaaS/Medios/EC: quieren estandarizar las directrices operativas, incluidas la cancelación de suscripción y la gestión de quejas
  • Especialistas en CS/Marketing/Accesibilidad: quieren dar forma a contenidos y políticas de notificaciones comprensibles para todo el mundo

Nivel de accesibilidad: ★★★★★

Trataremos la estructura del cuerpo del email, texto alternativo (alt), versión en texto plano, énfasis independiente del color, diseño del asunto y la vista previa, redacción de enlaces y una gestión clara de las suscripciones.


1. Introducción: Una notificación se basa en “Entrega”, “Comprensión” y “Elección”

El objetivo de las notificaciones es llegar de forma fiable a los usuarios, ser rápidamente entendidas y permitirles controlar cómo las reciben en cualquier momento. Laravel proporciona Mailables y Notifications, que te permiten gestionar múltiples canales desde la misma base de código. Pero la tecnología por sí sola no garantiza ni la entregabilidad ni la legibilidad. Debes pensar en la configuración de dominio, la cancelación de suscripción, la gestión de quejas, el diseño del mensaje y la accesibilidad como parte de la operación.

Este artículo es una guía práctica para diseñar e implementar una infraestructura de notificaciones que puedas llevar directamente a producción.


2. Arquitectura: Elegir entre Mailables y Notifications

2.1 ¿Cuál debería usar?

  • Mailable: Cuando quieres diseñar con detalle solo email (HTML/texto, adjuntos, control de layout).
  • Notifications: Cuando quieres difundir un único evento de notificación a través de Email/SMS/Slack/Push/Base de datos. Mantiene el código ordenado incluso cuando añades más canales más adelante.
// Ejemplo: Notification (Email + Base de datos)
class OrderShipped extends Notification
{
    use Queueable;

    public function via($notifiable): array
    {
        return ['mail','database'];
    }

    public function toMail($notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Your order has been shipped')
            ->greeting($notifiable->name.' ')
            ->line('Your order has been shipped. Here is your tracking number.')
            ->action('View shipment status', route('orders.track', $this->order->id))
            ->line('If you did not expect this email, you can safely ignore it.');
    }

    public function toDatabase($notifiable): array
    {
        return ['order_id' => $this->order->id, 'tracking' => $this->order->tracking_no];
    }
}

2.2 Las colas son obligatorias

Las notificaciones y los emails deberían siempre enviarse en cola. Esto evita perjudicar la experiencia de usuario y ayuda a manejar picos de tráfico.

// .env
QUEUE_CONNECTION=redis
MAIL_MAILER=smtp  // o un driver por API
Notification::route('mail', 'user@example.com')
    ->notify((new OrderShipped($order))->delay(now()->addSeconds(2)));

3. Configuración de infraestructura para la entregabilidad

3.1 Configuración de dominio (fundamentos operativos)

  • SPF: Declara qué remitentes están autorizados para tu dominio.
  • DKIM: Firma los mensajes salientes con tu dominio, ayudando a evitar manipulaciones.
  • DMARC: Política basada en los resultados SPF/DKIM, con informes para impulsar la mejora continua.
  • Usar un subdominio (por ejemplo, mail.example.com) te permite separar el dominio principal de tu app del usado para enviar correo y puede mejorar la entregabilidad.

3.2 Envío en la práctica

  • Cola y reintentos: Prepárate para problemas SMTP transitorios con reintentos de tipo “exponential backoff”.
  • Throttling: Limita el ritmo de envíos masivos y “calienta” tus IPs y dominios de envío.
  • Identidad del remitente: Configura From y Reply-To según el propósito. Distingue claramente direcciones de soporte y direcciones no-reply.
  • Estructura del mensaje: Usa multipart/alternative e incluye siempre una parte en texto plano. Evita los mensajes solo HTML.

4. Diseño de emails accesibles

Los emails se leen en clientes de correo, no en navegadores. El soporte de CSS, JavaScript y ARIA es limitado. Diseña con un conjunto mínimo de reglas que funcione de forma fiable.

4.1 Estructura del cuerpo

  • Usa encabezados, párrafos y listas para crear una jerarquía de información.
  • Presenta la información crítica (número de pedido, fechas, importes) como texto, no solo en imágenes.
  • Los textos de enlace deben ser específicos, no solo “aquí”. Usa “View shipment status”, etc.

4.2 Texto alternativo y contraste

  • Define siempre un atributo alt en las imágenes. Usa alt="" para imágenes decorativas.
  • Los botones deberían ser etiquetas <a> con estilo de botón, con un texto de etiqueta significativo. No te bases únicamente en el color.
  • Asegura suficiente contraste de color entre el fondo y el texto.

4.3 Proporciona siempre texto plano

  • Incluso donde HTML no está disponible, la versión en texto debe preservar el significado e información esenciales.
  • No partas las URLs entre líneas; mantén cada enlace en una sola línea sin cortes.

4.4 Asunto y texto de vista previa

  • Los asuntos deberían incluir contenido + identificador (por ejemplo, [Example] Shipment complete: Order #12345).
  • Coloca una frase de vista previa corta al principio (por ejemplo, You can track your shipment online.) para mejorar cómo se ve en la lista de bandeja de entrada.

4.5 Modo oscuro y layout

  • El estándar son layouts basados en tablas + CSS inline.
  • No puedes controlar completamente el modo oscuro. Evita texto sobre imágenes de fondo; mantén el diseño sencillo.

5. Gestión de plantillas con Mailables

5.1 Plantillas Blade

// app/Mail/OrderShippedMail.php
class OrderShippedMail extends Mailable implements ShouldQueue
{
    use Queueable, SerializesModels;

    public function __construct(public Order $order){}

    public function build()
    {
        return $this->subject('Shipment complete: Order #'.$this->order->number)
            ->from('no-reply@mail.example.com', config('app.name'))
            ->view('mail.orders.shipped')        // HTML
            ->text('mail.orders.shipped_text');  // Texto plano
    }
}
{{-- resources/views/mail/orders/shipped.blade.php --}}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
  <tr>
    <td>
      <h1 style="font-size:20px;line-height:1.4;margin:0 0 16px;">Your order has been shipped</h1>
      <p style="margin:0 0 12px;">Order number: {{ $order->number }}</p>
      <p style="margin:0 0 12px;">Total: ¥{{ number_format($order->total) }}</p>
      <p style="margin:0 0 20px;">You can check your shipment status using the button below.</p>
      <p>
        <a href="{{ route('orders.track', $order->id) }}"
           style="display:inline-block;background:#2563eb;color:#fff;padding:10px 16px;text-decoration:none;border-radius:4px;">
           View shipment status
        </a>
      </p>
      <p style="margin:20px 0 0;">※ This email is sent from a notification-only address. For questions, please contact support.</p>
    </td>
  </tr>
</table>
{{-- resources/views/mail/orders/shipped_text.blade.php --}}
Your order has been shipped.

Order number: {{ $order->number }}
Total: ¥{{ number_format($order->total) }}

Check your shipment status:
{{ route('orders.track', $order->id) }}

※ This email is sent from a notification-only address. For questions, please contact support.

5.2 Componentización

Convierte encabezados/pies comunes, botones de CTA y cajas de información en componentes Blade para unificar el texto y reducir el coste de mantenimiento.


6. Cancelación de suscripción y gestión de suscripciones

6.1 Cumplimiento legal y UX

  • Todo email de marketing debería tener una ruta de cancelación de suscripción claramente visible (pie o cabecera).
  • Las notificaciones transaccionales (recibos, etc.) deberían ser una categoría separada, para evitar que se desactiven por error.
  • Una página de ajustes que permita a los usuarios controlar temas/frecuencia puede reducir significativamente los tickets de soporte.

6.2 Implementación (enlaces firmados)

// Enlace de cancelación (con expiración)
$url = URL::temporarySignedRoute(
    'unsubscribe',
    now()->addDays(7),
    ['user' => $user->id, 'topic' => 'marketing']
);
// routes/web.php
Route::get('/unsubscribe', function(Request $r){
    abort_unless($r->hasValidSignature(), 403);
    $user = User::findOrFail($r->integer('user'));
    $user->unsubscribe($r->string('topic'));
    return view('mail.unsubscribe_done');
})->name('unsubscribe');

6.3 Cabecera List-Unsubscribe

Configurar la cabecera List-Unsubscribe ayuda a que algunos clientes muestren una interfaz de “unsubscribe” con un solo clic. Usa también aquí una URL firmada.


7. Webhooks de entrada: rebotes, quejas, supresión

7.1 Por qué lo necesitas

Para mantener una buena entregabilidad, debes recibir y reaccionar a las señales de feedback. Las direcciones de email con hard bounce o que generan quejas deben excluirse de futuros envíos.

7.2 Flujo

  1. Proporciona un endpoint que reciba Webhooks de tu proveedor de email.
  2. Valida la autenticidad mediante verificación de firma.
  3. Clasifica los tipos de evento (hard/soft bounce, queja, apertura/click si se registran).
  4. Actualiza la lista de supresión, bloqueando futuros envíos a esos destinatarios.
  5. Refleja los eventos en las métricas.
// Verificación de firma (esquema)
$payload = $request->getContent();
$sig = $request->header('X-Signature');
$calc = hash_hmac('sha256', $payload, config('services.mail.webhook_secret'));
abort_unless(hash_equals($calc, $sig), 401);

8. Multiidioma, zonas horarias y personalización

8.1 Locale y placeholders

  • Usa ->locale() en MailMessage y Mailable.
  • Usa claves de traducción + variables para generar frases completas, evitando gramática antinatural.
  • Sanea nombres, importes y fechas, y proporciona siempre valores por defecto para datos faltantes.

8.2 Timing

  • Ajusta las horas de envío a la zona horaria del usuario.
  • Los resúmenes diarios/semanales pueden reducir la fatiga del usuario y las bajas de suscripción.

9. SMS, Push y notificaciones in-app: orquestación de canales

9.1 Notificaciones escalonadas

  • Comienza con notificaciones in-app (base de datos) como canal silencioso.
  • Usa email para asuntos importantes y escala a SMS/Push solo para eventos urgentes.
  • Cada canal debería llevar al mismo texto clave y la misma acción, para evitar confusión.

9.2 Consideraciones para SMS

  • Ten presentes los límites de caracteres y los acortadores de URL. Comunica los puntos clave de forma concisa y concreta.
  • Respeta las horas de descanso y las preferencias del usuario; evita SMS a altas horas de la noche.

9.3 Web Push

  • Obtén un consentimiento explícito, haz que darse de baja sea fácil y clasifica adecuadamente las notificaciones y su prioridad.

10. Medición, monitorización y paneles

  • Agrega envíos, aciertos/fallos, tipos de rebotes, quejas, aperturas/clicks (si se registran) por día y por plantilla.
  • Lo más importante son las tendencias: configura alertas para picos anómalos (por ejemplo, tasa de quejas) y revisa contenido y listas de destinatarios.
  • Maneja los datos de comportamiento identificables personalmente con una estricta minimización, respetando las políticas de privacidad y las decisiones del usuario.

11. Estrategia de pruebas: fakes, renderizado, accesibilidad

11.1 Mail/Notification Fakes

use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;

Mail::fake();
Notification::fake();

$user = User::factory()->create();
Notification::send($user, new OrderShipped($order));

Notification::assertSentTo($user, OrderShipped::class, function($n, $channels){
    return in_array('mail', $channels);
});

11.2 Pruebas de renderizado

  • Genera las versiones HTML y texto como strings y comprueba el contenido esencial (asunto, CTA, número de pedido, etc.).
  • Comprueba que no falten atributos alt= y que las URLs sean absolutas.
$mailable = new OrderShippedMail($order);
$html = $this->renderMailable($mailable); // Helper propio para obtener el HTML renderizado
$this->assertStringContainsString('View shipment status', $html);
$this->assertMatchesRegularExpression('/alt="[^"]*"/', $html);

11.3 Previews y revisiones visuales manuales

  • Proporciona una ruta de preview en tu entorno local y revisa visualmente en los principales clientes.
  • Las pruebas de captura de pantalla completa en CI son complicadas, pero las comprobaciones estáticas de HTML y de enlaces aportan mucho valor.

12. Ejemplo: ajustes de suscripción y esqueleto de plantilla

12.1 Modelo Subscription

// app/Models/Subscription.php
class Subscription extends Model {
  protected $fillable = ['user_id','topic','enabled'];

  public static function enabled($user, $topic): bool {
    return static::where(compact('user','topic'))
        ->where('enabled', true)
        ->exists();
  }
}

12.2 Filtrado en el momento de la notificación

if (! Subscription::enabled($user->id, 'marketing')) {
    return; // No enviar
}

$user->notify(new CampaignStarted($campaign));

12.3 UI de cancelación firmada (extracto)

@extends('layouts.app')
@section('title','Email preferences')
@section('content')
<h1 class="text-xl font-semibold mb-4" id="title" tabindex="-1">Email delivery settings</h1>
<p class="mb-3">You can control whether to receive each topic.</p>
<ul class="space-y-2">
  @foreach($topics as $topic)
  <li>
    <form method="POST" action="{{ route('settings.subscribe.toggle') }}">
      @csrf
      <input type="hidden" name="topic" value="{{ $topic->name }}">
      <button class="px-3 py-1 rounded border">
        {{ $topic->label }}: {{ $topic->enabled ? 'Receive' : 'Do not receive' }}
      </button>
    </form>
  </li>
  @endforeach
</ul>
@endsection

13. Errores comunes y cómo evitarlos

  • Enviar solo HTML → Usa multipart/alternative e incluye siempre una versión en texto.
  • Poner información crítica en imágenes → Duplícala siempre en texto.
  • Enlaces tipo “Haz clic aquí” por todas partes → Usa textos de enlace específicos y orientados a la acción.
  • Cancelación difícil de encontrar → Añade un pie de página claro + cabecera List-Unsubscribe.
  • Ignorar quejas/rebotes → Usa Webhooks y actualiza una lista de supresión.
  • Gestión ambigua del consentimiento → Usa doble opt-in y permite elegir temas/frecuencia.
  • SMS nocturnos → Diseña teniendo en cuenta zonas horarias y horas de silencio.
  • Enviar sin colas → Riesgo de respuestas lentas y mayor tasa de fallos. Las colas son esenciales.
  • Seguimiento excesivamente agresivo → Respeta la privacidad, registra datos mínimos y sé transparente.

14. Checklist (para compartir con el equipo)

Entregabilidad

  • [ ] SPF/DKIM/DMARC configurados
  • [ ] Envío en cola, reintentos, throttling
  • [ ] Subdominio/From/Reply-To definidos claramente

Contenido y accesibilidad

  • [ ] El asunto incluye contenido + identificador, hay texto de vista previa
  • [ ] Formato dual HTML + texto
  • [ ] Las imágenes tienen alt, no se usa solo el color para enfatizar, texto de enlace específico
  • [ ] Información crítica mostrada como texto

Gestión de suscripciones

  • [ ] Enlace de cancelación y página de ajustes
  • [ ] Cabecera List-Unsubscribe
  • [ ] Distinción entre email transaccional y de marketing

Webhooks y supresión

  • [ ] Verificación de firma
  • [ ] Clasificación de rebotes duros/blandos y quejas, actualización de lista de supresión
  • [ ] Métricas y alertas

Multiidioma y personalización

  • [ ] Uso de ->locale() y claves de traducción
  • [ ] Gestión de zonas horarias, opciones de resumen/frecuencia
  • [ ] Valores por defecto para datos faltantes

Pruebas y operación

  • [ ] Mail/Notification fakes
  • [ ] Comprobaciones de renderizado de plantillas
  • [ ] Entorno de preview y validación de enlaces
  • [ ] Logs de envío e IDs de petición

15. Resumen

  • Basa tu sistema en Laravel Mailables/Notifications y garantiza la entregabilidad mediante envío en cola y una configuración de dominio adecuada.
  • Usa emails HTML + texto, con texto alternativo, atención al contraste y enlaces específicos para maximizar la legibilidad y accesibilidad.
  • Permite que los usuarios elijan mediante una cancelación y gestión de suscripciones claras, separando emails transaccionales y de marketing.
  • Recoge rebotes/quejas a través de Webhooks, actualizando listas de supresión y métricas.
  • Combina SMS/Push/notificaciones in-app por etapas para diseñar una experiencia de notificación efectiva pero no invasiva.
  • Integra pruebas y previsualización en tus operaciones para crear una plataforma de notificaciones robusta y de baja tasa de fallos. Notificaciones serenas y honestas se convierten en la base de una confianza a largo plazo.

Referencias

por greeden

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)