Tutorial
Enviar emails con Laravel
Envía el correo de Laravel a través de Emailit con el transporte de correo y la facade de emailit/emailit-laravel, o con SMTP sin más, y verifica los webhooks de Emailit.
En esta guía se explican dos formas de enviar el correo de Laravel a través de Emailit: el paquete emailit/emailit-laravel, que añade un transporte de correo emailit y una facade Emailit, y SMTP sin más con el mailer integrado de Laravel. Al final se incluye una ruta de webhook que verifica las firmas.
Requisitos previos
- PHP 8.1 o posterior y Laravel 10, 11 o 12.
- Un dominio de envío verificado, por ejemplo
acme.com. - Una clave de API. Basta con una clave Sending Only limitada a tu dominio.
- Hasta que tu espacio de trabajo tenga acceso de producción, solo puedes enviar a las direcciones de email de las cuentas de los miembros del espacio de trabajo.
Opción 1: el paquete de Laravel de Emailit
El paquete envía tus Mailables, Markdown mailables, notificaciones y correo en cola actuales a través de la API de Emailit, sin cambiar tu código de correo.
Instalar el paquete
composer require emailit/emailit-laravelEl service provider se detecta automáticamente.
Configurar el transporte
Añade tu clave y haz que Emailit sea el mailer por defecto en .env:
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
MAIL_MAILER=emailit
MAIL_FROM_ADDRESS=hello@acme.com
MAIL_FROM_NAME="Acme"Registra el mailer en config/mail.php:
'mailers' => [
// ...
'emailit' => [
'transport' => 'emailit',
],
],MAIL_FROM_ADDRESS debe pertenecer a un dominio de envío verificado. Para cambiar la URL base de la API, publica el archivo de configuración con php artisan vendor:publish --tag=emailit-config; para un uso normal no hace falta.
Enviar un Mailable
Crea un Mailable como de costumbre:
php artisan make:mail WelcomeEmailnamespace 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 WelcomeEmail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(public User $user) {}
public function envelope(): Envelope
{
return new Envelope(subject: 'Welcome to Acme');
}
public function content(): Content
{
return new Content(view: 'emails.welcome');
}
}Envíalo, o ponlo en cola para que la petición no espere a la API:
use App\Mail\WelcomeEmail;
use Illuminate\Support\Facades\Mail;
Mail::to($user)->send(new WelcomeEmail($user));
Mail::to($user)->queue(new WelcomeEmail($user));Usar la facade para las funciones de la API
La facade Emailit expone todo el SDK de PHP para las funciones que el mailer de Laravel no contempla, como las plantillas guardadas y los envíos programados:
use Emailit\Laravel\Facades\Emailit;
$email = Emailit::emails()->send([
'from' => 'Acme <hello@acme.com>',
'to' => $user->email,
'template' => 'welcome',
'variables' => ['first_name' => $user->first_name],
'scheduled_at' => 'tomorrow at 9am',
]);
$email->id; // em_…La facade también cubre los dominios, los contactos, las listas de contactos, las direcciones bloqueadas, los webhooks y los demás recursos. Si prefieres la inyección de dependencias, declara el tipo Emailit\EmailitClient en un controlador o un job y Laravel lo resuelve con la clave que has configurado.
Captura las excepciones tipadas para gestionar los fallos:
use Emailit\Exceptions\ApiErrorException;
use Emailit\Exceptions\RateLimitException;
try {
Emailit::emails()->send($payload);
} catch (RateLimitException $e) {
// 429: release the job back to the queue and try again later
} catch (ApiErrorException $e) {
report($e); // $e->getHttpStatus() has the status code
}Opción 2: SMTP sin más
Si prefieres no añadir un paquete, apunta el mailer SMTP de Laravel al relay de Emailit:
MAIL_MAILER=smtp
MAIL_HOST=smtp.emailit.com
MAIL_PORT=587
MAIL_USERNAME=emailit
MAIL_PASSWORD=secret_••••••••••••••••••••••••••••••••
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=hello@acme.com
MAIL_FROM_NAME="Acme"En el puerto 587, el mailer de Laravel actualiza la conexión con STARTTLS. Los archivos config/mail.php antiguos leen MAIL_ENCRYPTION y los nuevos lo ignoran, así que puedes mantenerlo sin problemas. Para TLS implícito, usa el puerto 465; si tu proveedor de hosting bloquea el 587, usa 2525 o 2587. Los Mailables, las notificaciones y las colas funcionan exactamente igual que con el paquete. Consulta Configuración SMTP.
Por SMTP no puedes usar plantillas guardadas ni scheduled_at; para eso, usa la facade o la API.
Recibir webhooks
Crea un webhook que apunte a https://your-app.com/webhooks/emailit y después guarda su secreto de firma:
EMAILIT_WEBHOOK_SECRET=whsec_••••••••'emailit' => [
'webhook_secret' => env('EMAILIT_WEBHOOK_SECRET'),
],Añade un controlador que compruebe la firma con el cuerpo en bruto:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class EmailitWebhookController extends Controller
{
public function __invoke(Request $request)
{
$payload = $request->getContent();
$signature = (string) $request->header('X-Emailit-Signature');
$timestamp = (string) $request->header('X-Emailit-Timestamp');
$expected = hash_hmac('sha256', $timestamp.'.'.$payload, config('services.emailit.webhook_secret'));
if (abs(time() - (int) $timestamp) > 300 || ! hash_equals($expected, $signature)) {
abort(401, 'Invalid signature');
}
// The body is a JSON array of up to 100 events.
foreach (json_decode($payload, true) as $event) {
match ($event['type']) {
'email.bounced', 'email.complained' => $this->stopEmailing($event['data']['object']['to']),
default => null,
};
}
return response()->noContent();
}
private function stopEmailing(string $address): void
{
// Mark the address as undeliverable in your database.
}
}Registra la ruta y exclúyela de la protección CSRF, porque Emailit no puede enviar un token CSRF:
use App\Http\Controllers\EmailitWebhookController;
Route::post('/webhooks/emailit', EmailitWebhookController::class);->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: ['webhooks/emailit']);
})En Laravel 10, añade en su lugar 'webhooks/emailit' al array $except de app/Http/Middleware/VerifyCsrfToken.php. Devuelve un 2xx en menos de 30 segundos y pasa el trabajo lento a una cola. Consulta Firma de las peticiones.
Consejos para producción
- Pon el correo en cola. Usa
queue()oShouldQueuepara que las peticiones web no esperen al email, y limita el ritmo de los jobs masivos (por ejemplo, conRedis::throttle) para no superar tus límites de envío. Por defecto, los espacios de trabajo nuevos pueden enviar 2 emails por segundo. - Usa la caché de configuración de forma segura. Después de
php artisan config:cache,env()solo funciona dentro de los archivos de configuración. Lee la clave a través de la configuración, como hace el paquete. - Usa una clave dedicada. Da a cada aplicación y a cada entorno su propia clave Sending Only para poder rotar una sin tocar las demás. Consulta Claves de API.
- Elimina los webhooks duplicados. Guarda cada
event_idque procesas y omite las repeticiones, porque las entregas fallidas se reintentan.