Envío de correo y SMS
Los transportes de correo que el kit conoce, las llaves que lee cada uno y los dos proveedores que pueden llevar un mensaje de texto.
Esta guía cubre cómo sale un correo de la aplicación y cómo sale un mensaje de texto: qué transporte se usa, qué variables de entorno lee cada uno, qué pasa cuando un transporte falla y cómo ver el correo mientras desarrollas. La necesitas antes de que se registre la primera persona, porque el código de verificación viaja sobre estos ajustes.
Qué mailer envía
config/mail.php lee MAIL_MAILER para elegir el mailer por omisión, y .env.example trae MAIL_MAILER=failover. Abajo está cada mailer que define el archivo; el valor que escribes en MAIL_MAILER es el nombre de la primera columna.
| Mailer | Qué hace | Qué lee |
|---|---|---|
smtp |
Habla con un servidor SMTP | MAIL_HOST (por omisión 127.0.0.1), MAIL_PORT (por omisión 2525), MAIL_USERNAME, MAIL_PASSWORD; opcionalmente MAIL_SCHEME, MAIL_URL y MAIL_EHLO_DOMAIN, que por omisión es el host de APP_URL |
ses |
Amazon SES a través del SDK de AWS | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION (para SES cae a us-east-1) |
mailgun |
La API de Mailgun | MAILGUN_DOMAIN, MAILGUN_SECRET, MAILGUN_ENDPOINT (por omisión api.mailgun.net; usa api.eu.mailgun.net para un dominio europeo) |
postmark |
La API de Postmark | POSTMARK_TOKEN, opcionalmente POSTMARK_MESSAGE_STREAM_ID; el cliente se rinde a los 5 segundos |
resend |
La API de Resend | RESEND_KEY |
sendmail |
El binario local sendmail |
MAIL_SENDMAIL_PATH (por omisión /usr/sbin/sendmail -bs -i) |
log |
Escribe el mensaje completo en el log en vez de enviarlo | MAIL_LOG_CHANNEL; vacío significa el canal de log por omisión |
array |
Guarda los mensajes en memoria; para pruebas | Nada |
failover |
Intenta mailgun y luego ses |
Las llaves de ambos |
roundrobin |
Alterna entre ses y postmark |
Las llaves de ambos |
Dos están configurados pero no instalados: el transporte postmark necesita symfony/postmark-mailer y el de resend necesita resend/resend-php, y ninguno está en composer.json. Instala el paquete antes de apuntar MAIL_MAILER o una entrada de roundrobin hacia ellos. Mailgun (symfony/mailgun-mailer) y SES (aws/aws-sdk-php) sí están instalados.
Cómo se comporta failover
El mailer failover envía por el primer mailer de su lista que acepte el mensaje. Con la lista que viene, ése es Mailgun; cuando Mailgun lanza un error, el mismo mensaje va a SES. Un mailer que falló se omite durante retry_after segundos, 60 en el archivo, antes de intentarlo otra vez. Nada se reintenta después: cuando todos los mailers de la lista fallan, el envío falla.
Por eso un .env recién creado no envía nada. failover necesita las llaves de Mailgun y las de SES; sin ninguna, ambos fallan. Elige un transporte real:
MAIL_MAILER=mailgun
MAILGUN_DOMAIN=mg.your-project.test
MAILGUN_SECRET=key-xxxxxxxx
o conserva failover y llena los dos proveedores. Edita la lista mailers de config/mail.php cuando tu pareja sea otra, por ejemplo ses y luego smtp.
MAILGUN_DOMAIN tiene mg.weblabor.mx como valor por omisión en config/services.php. Ése es el dominio de envío de Weblabor; pon el tuyo o Mailgun rechaza tu correo.
El remitente
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
MAIL_FROM_ADDRESS tiene [email protected] como valor por omisión y MAIL_FROM_NAME toma APP_NAME. .env.example trae [email protected]: cámbialo, porque todo proveedor envía sólo desde un dominio que verificaste con él.
El correo de verificación y mail_fallbacks
El código que una persona escribe para verificar su correo no es una notificación. Es el Mailable App\Mail\Auth\ValidateEmail, con el asunto "Email validation: {nombre de la app}" y la vista Markdown resources/views/emails/auth/validate_email.blade.php, enviado por App\Livewire\Auth\Traits\NeedsVerification a través de la fachada Mail. El código son diez letras y dígitos al azar.
Ese trait es el único que lee mail_fallbacks en config/auth.php:
'mail_fallbacks' => [
'mailgun',
],
Arma una lista que empieza con MAIL_MAILER y sigue con estos nombres. El primer envío usa la primera entrada; cada pulsación de Reenviar código pasa a la siguiente, haya fallado la anterior o simplemente no haya llegado. Cuando la lista se agota, Reenviar muestra "No se pudo enviar el código" y sugiere otra dirección. Una vez confirmado el código, el nombre del mailer que funcionó se guarda en el extra_data del usuario bajo mail_driver_used. Con los valores que vienen, el orden es failover y luego mailgun en el primer reenvío; escribe los transportes que de verdad tienes, en el orden en que quieres que se prueben.
Las otras vistas de correo
El kit renderiza tres correos propios con los componentes de correo Markdown de Laravel (<x-mail::message>, <x-mail::button>):
| Vista | Se envía cuando |
|---|---|
resources/views/emails/auth/validate_email.blade.php |
Una persona verifica su correo |
resources/views/emails/announcements/announcement.blade.php |
Se publica un aviso con "enviar correo": título, cuerpo y un botón Leer aviso |
resources/views/emails/user/update_role_email.blade.php |
Un administrador agrega o quita un rol |
Todas las demás notificaciones usan la plantilla MailMessage por omisión de Laravel. Para cambiarles el estilo a todas, publica los componentes de correo con php artisan vendor:publish --tag=laravel-mail y edita resources/views/vendor/mail.
Mensajes de texto
No hay paquete de SMS. El kit trae un canal de notificación, App\Channels\SmsChannel, y un servicio detrás de él, App\Services\SnsService, que publica a través de Amazon SNS.
El canal
SmsChannel::send() le pide al notificable routeNotificationFor('sms'), cae a su atributo phone, y llama a toSms() en la notificación para obtener el texto. Después SnsService::sendSms($phone, $message) lo publica. El cliente se construye con AWS_DEFAULT_REGION (por omisión us-west-1), AWS_ACCESS_KEY_ID y AWS_SECRET_ACCESS_KEY, las mismas credenciales que leen el mailer ses y el disco s3, y cada mensaje sale con SMSType Transactional, la clase de entrega que las operadoras priorizan. Una publicación rechazada se reporta al log y regresa false; el canal entonces lanza un error, así que la notificación cuenta como fallida.
Úsalo en una notificación tuya:
<?php
namespace App\Notifications\Orders;
use App\Channels\SmsChannel;
use App\Models\Order;
use Illuminate\Notifications\Notification;
class OrderReady extends Notification
{
public function __construct(public Order $order) {}
public function via(object $notifiable): array
{
return [SmsChannel::class];
}
public function toSms(object $notifiable): string
{
return __(':app - Your order :number is ready', [
'app' => config('app.name'),
'number' => $this->order->number,
]);
}
}
El teléfono debe estar en formato internacional: +52 55 1234 5678 escrito como +525512345678. El canal envía lo que tenga la columna phone; el kit guarda los teléfonos normalizados al registrarse, así que un teléfono que escribas tú te toca normalizarlo, y normalize_phone_number($value) regresa la forma E.164 o null.
Este canal está separado de la clase base App\Notifications\Notification: una notificación que extiende la base va a la base de datos, al correo y a web push, nunca a SMS, y ninguna preferencia del usuario apaga el SMS. Mira /help/send-notifications.
Quién envía el código de verificación del teléfono
AUTH_VALIDATION_PROVIDER decide qué proveedor lleva el código de cuatro dígitos de /help/verify-email-and-phone. User::sendVerificationCode() lo lee:
| Valor | Qué pasa |
|---|---|
aws (por omisión) |
Se notifica al usuario con App\Notifications\Auth\VerificationCodeNotification, que pasa por SmsChannel y SNS con el texto "{app} - Tu código de verificación es: {código}" |
telnyx |
App\Services\TelnyxVerifyService::sendVerification($phone, $code, $method) llama a Telnyx Verify, por sms o por call; el diálogo de verificación ofrece ambos, Recibir una llamada incluido |
Telnyx lee TELNYX_API_KEY (con TELNYX_TOKEN como respaldo) y TELNYX_VERIFY_PROFILE_ID, el id de un perfil de Verify creado en el portal de Telnyx. El servicio normaliza primero el teléfono con normalize_phone_number() y regresa false, sin llamar a nadie, cuando el teléfono no se puede interpretar o falta alguna de las dos llaves; el diálogo entonces dice "No se pudo enviar el código". .env.example trae AUTH_VALIDATION_PROVIDER=aws y ambas llaves de Telnyx con valores de relleno.
Cada envío queda registrado
App\Listeners\LogNotificationSent y App\Listeners\LogNotificationFailed escuchan los eventos de notificación de Laravel y escriben una fila en communication_logs por cada notificación que pasa por el canal mail o por SmsChannel: el usuario, el destinatario, la clase de la notificación, el asunto y el cuerpo HTML renderizado de un correo enviado, y sent o failed. Las entregas a base de datos y a web push no se registran. Dos envíos se saltan a los listeners porque no son notificaciones: el correo de verificación, enviado con la fachada Mail, y un código de Telnyx. Los administradores leen la tabla en /admin/communication_logs; mira /help/audit-trails-and-webhook-events.
Ver el correo mientras desarrollas
Nada en el kit simula un proveedor, así que usa los transportes hechos para eso:
MAIL_MAILER=log
escribe cada mensaje, encabezados y cuerpo, en el canal de log por omisión, que es storage/logs/laravel-YYYY-MM-DD.log a menos que LOG_CHANNEL diga otra cosa; pon MAIL_LOG_CHANNEL para mandarlos a un canal propio. O apunta smtp a un receptor local como Mailpit:
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
y lee los mensajes en su bandeja. Ambos sirven también para el correo de verificación, porque mail_fallbacks empieza con MAIL_MAILER.
Para SMS no hay equivalente. Sin credenciales de AWS o de Telnyx un código por teléfono no se puede enviar y el diálogo lo dice; mientras no las tengas, regístrate con un correo o pon AUTH_ENABLE_VALIDATION=false en local.