Índice de contenido
- Clase Mailable
- Enviar correos de forma individual
- Parámetros
- CC y BCC/CCO
- Enviar correos en masa
- Enviar mensajes de verificación de email del usuario en Laravel
- Mandar mensajes de confirmación de manera programática
- Extra: Rest Api para los usuarios a verificar
- TRUCAZO para enviar correos Automatizados y MASIVOS en Laravel Livewire
- Implementación
- 1. Manejo del Contador (subpage)
- 2. La función de envío (sendEmailSubscribers)
- 3. El Secreto: wire:poll y always
- Optimización y Concurrencia
- Alternativa con JavaScript
Conociendo como podemos limitar la cantidad de peticiones del usuario, resulta buena idea aprener a enviar correos para notificar a estos usuarios. Podemos configurar el envío de correos muy fácilmente en Laravel, si ya tienes un servicio que provee tu hosting o similar, lo único que debes de hacer es crear una dirección de correo con su contraseña.
A partir de aquí, debes de configurar algunos parámetros, si no los conoces porque el servidor de correos que estás empleando es de un servicio, debes de preguntar a tus proveedores de servicio; en el caso de Hostinger serían los siguientes:
config/mail.php
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST', 'smtp.hostinger.com'),
'port' => env('MAIL_PORT', 465),
'encryption' => env('MAIL_ENCRYPTION', 'ssl'),
'username' => env('MAIL_USERNAME','<EMAIL>'),
'password' => env('MAIL_PASSWORD',"<PASSWORD>"),
'timeout' => null,
'auth_mode' => null,
],O puedes emplear un servicio de pruebas en caso de que no tengas acceso a un servidor de correos real como mailtrap:
En el cual, debes de ir a la página anterior, crearte una cuenta que es completamente gratuita, crear un inbox y configurar en tu proyecto.
.env
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=ec5cbede982042
MAIL_PASSWORD=********0be7Clase Mailable
Para enviar cualquier correo, debemos de emplear una clase, al igual que ocurre cuando definimos un modelo, controlador o request, debemos de crear una clase con una estructura específica:
app\Mail\OrderShipped.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class OrderShipped extends Mailable
{
public $email;
public $title;
public $content;
use Queueable, SerializesModels;
public function __construct($email, $title, $content)
{
$this->email = $email;
$this->title = $title;
$this->content = $content;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Order Shipped',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.subscribe',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}Para ello, usamos el comando de:
$ php artisan make:mail OrderShippedEl método de envelope() lo empleamos para definir el asunto, el de content() para el cuerpo del mensaje, allí especificamos la vista y el de attachments() para archivos adjuntos, el constructor es básico en las clases de PHP y se emplea para inicializar propiedades u otras tareas al momento de crear el objeto o instancia de la clase.
También definimos algunas propiedades de ejemplo como lo son el título, contenido e email, puedes crear otros o modificar los definidos según tus necesidades.
También podemos emplear un solo método para definir el asunto y contenido:
app\Mail\SubscribeEmail.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class SubscribeEmail extends Mailable
{
use Queueable, SerializesModels;
public $email;
public $title;
public $content;
public function __construct($email, $title, $content)
{
$this->email = $email;
$this->title = $title;
$this->content = $content;
}
public function build()
{
return $this->subject($this->title)->view('subscribe');
}
}Desde la misma, podemos personalizar los argumentos a recibir, como email asunto o contenido del correo y retornar una vista de blade que corresponde al cuerpo del correo; por ejemplo:
Creamos la vista que puede tener cualquier formato:
resources\views\emails\subscribe.blade.php
<p>Hi<br>
{!! $content !!}Enviar correos de forma individual
Para enviar los correos, creamos una instancia de la siguiente manera:
Mail::to('no-reply@example.net.com')->send(new SubscribeEmail('contact@gmail.com', $title, $content));Parámetros
No está limitado a especificar simplemente los destinatarios "to" al enviar un mensaje. Eres libre de configurar destinatarios "to", "cc" y "bcc" empleando sus respectivos métodos:
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new SubscribeEmail(contact@gmail.com', $title, $content));CC y BCC/CCO
Un CC es una forma de enviar copias adicionales de un correo electrónico a otras personas "copia de carbono", mientras que BCC es lo mismo que el CC pero esta lista de destinatarios o personas permanecen ocultas, es decir, no aparecen en la firma del correo electrónico.
Enviar correos en masa
Una de las formas en las cuales podemos enviar múltiples correos en masa o lote, es la de enviar en un mismo contacto a múltiples correos, usualmente este es un caso delicado ya que, si hacemos algo como lo siguiente:
Mail::to('no-reply@example.net.com')
->cc(['hideemail1@gmail.com','hideemail2@gmail.com','hideemail3@gmail.com'])->send(new SubscribeEmail(contact@gmail.com', $title, $content));El correo va a exponer todos los emails de los usuarios, situación que usualmente es un problema por la exposición de todos los correos a todos los destinatarios; en vez de emplear la opción de cc podemos emplear la opción de bcc la cual permite ocultar los destinatarios:
Mail::to('no-reply@example.net.com')
->bcc(['hideemail1@gmail.com','hideemail2@gmail.com','hideemail3@gmail.com'])->send(new SubscribeEmail(contact@gmail.com', $title, $content));Ahora veremos que los emails definidos en BBC aparecen ocultos:
Y no presentes como en la imagen anterior.
Enviar mensajes de verificación de email del usuario en Laravel
Los mensajes de confirmación son correos que se envían a un cliente al completar una acción para confirmar la operación realizada, por ejemplo, el cambio de una contraseña, recuperar un usuario, la compra de un producto, etc, esto en cualquier sistema web que incluye alguna interacción con el usuario, es una tarea fundamental, por lo tanto, aprenderemos a enviar correos desde Laravel.
En Laravel, tenemos acceso a una función que podemos utilizar para confirmar el registro de un usuario.
Mandar mensajes de confirmación de manera programática
Desde una instancia del usuario, tenemos acceso a una función que envía el email de confirmación para verificar un usuario por el email:
$user->sendEmailVerificationNotification();Por supuesto, para esto debes de tener configurado tu servidor SMTP para el envio de correos.
Y recuerda implementar en tu modelo de usuarios la clase MustVerifyEmail:
use Illuminate\Contracts\Auth\MustVerifyEmail;
***
class User extends Authenticatable implements MustVerifyEmailYa con esto, puedes hacer uso de la función anterior; al usarla, llegará un correo como el siguiente:

Extra: Rest Api para los usuarios a verificar
Por aquí te dejo un uso común de esta función sobre una Rest Api para registrar usuarios y verificar los mismos; tenemos una función exclusiva para mandar los emails de verificación de los usuarios:
class UserController extends Controller
{
/**
* Register
*/
public function register(Request $request)
{
$validator = Validator::make($request->all(), StoreUser::myRules());
if ($validator->fails())
return $this->errorResponse($validator->errors(), 422);
try {
$user = new User();
$user->name = $request->name;
$user->email = $request->email;
$user->password = Hash::make($request->password);
$user->save();
if ($request->subscribed) {
Subscribe::create(['email' => $request->email]);
}
$success = true;
$message = 'User register successfully';
} catch (\Illuminate\Database\QueryException $ex) {
$success = false;
$message = $ex->getMessage();
}
// response
$response = [
'success' => $success,
'message' => $message,
];
$credentials = [
'email' => $request->email,
'password' => $request->password,
];
Auth::attempt($credentials);
$user->sendEmailVerificationNotification();
return $this->successResponse($response);
}
public function verifie()
{
$user = Auth::user() ?? auth('sanctum')->user();
$userModel = User::find($user->id);
$userModel->sendEmailVerificationNotification();
return $this->successResponse("ok");
}Como ves, tenemos una función de registrarse, en la cual obtenemos los datos del usuario y una vez registrado, iniciamos sesión con sendEmailVerificationNotification y enviamos el correo con la función explicada en esta entrada.
Y una función de verificar para que la ejecutes de manera programática; en este ejemplo, estamos empleando una Rest Api en Laravel con Laravel Sanctum con tokens, pero puedes emplear cualquier tipo para verificar cuentas de usuario.
TRUCAZO para enviar correos Automatizados y MASIVOS en Laravel Livewire
Te voy a mostrar cómo enviar correos electrónicos de forma automatizada cada 7, 5 o 4 segundos (o el intervalo que tú determines) utilizando Laravel Livewire. Como puedes ver, cada vez que el contador cambia, se envía un email a mis suscriptores.
Este esquema es sumamente eficiente: lo he utilizado para enviar unos 1,000 correos en menos de dos horas. Si tienes una base de datos de, por ejemplo, 9,000 usuarios, puedes sacar tus cuentas o incluso ejecutar el proceso en paralelo abriendo varias pestañas del navegador. Si no utilizas Livewire, no te preocupes; puedes implementar este mismo esquema con un poco de JavaScript.
El panel que utilizo para gestionar los envíos. El contenido del email se redacta en HTML utilizando el plugin CKEditor. He publicado varios vídeos y artículos aquí en mi blog sobre cómo integrar este editor con Laravel por si te interesa profundizar en ello.
Implementación
El núcleo de esto es un componente de Livewire, aprovechando su naturaleza reactiva. Aquí te explico los puntos clave:
1. Manejo del Contador (subpage)
Utilizo una propiedad llamada subpage que comienza en cero. Esto es fundamental por si ocurre un error (falla el internet o el servidor). Si el proceso se detiene, puedo ver cuál fue el último bloque enviado y retomar desde ahí (por ejemplo, desde la página 50). Esto me permite enviar bloques de 300 correos, dejar descansar el servidor y continuar después.
<?php
namespace App\Livewire\Dashboard\Blog;
use App\Mail\SubscribeEmail;
use App\Models\Subscribe;
use App\Models\SubscriptionContent;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Support\Facades\Config;
class SubscribeSendEmail extends Component
{
***
public $subPage = 0; // sub actual
***
// envia el correo a cada sub
function sendEmailSubscriptions()
{
// ENVIAR A TODOS
$subscribe = Subscribe::select('name', 'email', 'id')
->where('active', $this->typeSubscriptors) // Grupo de Subs seleccionado
->offset($this->subPage) // pagina que va variando en el poll
->limit(1) // solo uno
->orderBy('id')->first();
}
}2. La función de envío (sendEmailSubscribers)
Busco al usuario en mi tabla de suscriptores utilizando un límite de uno (limit(1)->first()). Mientras existan registros, el proceso continúa; de lo contrario, se detiene.
class SubscribeSendEmail extends Component
{
***
// envia el correo a cada sub
function sendEmailSubscriptions()
{
// ENVIAR A TODOS
$subscribe = Subscribe::select('name', 'email', 'id')
->where('active', $this->typeSubscriptors) // Grupo de Subs seleccionado
->offset($this->subPage) // pagina que va variando en el poll
->limit(1) // solo uno
->orderBy('id')->first();
***
}3. El Secreto: wire:poll y always
La verdadera "magia" de Livewire en este esquema es la directiva wire:poll. Esta instrucción le indica al componente cada cuánto tiempo debe refrescarse (llamarse a sí mismo).
- Intervalo: Yo utilizo 7 segundos. He probado con 3 o 4 segundos y funciona bien, pero 7 es un margen seguro para no colapsar servicios de terceros (como Hostinger o Gmail).
- Modo keep-alive: Uso el modificador .always para que, si cambias de pestaña en el navegador, el proceso no se detenga. Esto permite que el envío continúe en segundo plano.
Y el blade:
resources/views/livewire/dashboard/subscribe-send-email.blade.php
@if ($active)
<div wire:poll.7000ms.keep-alive="startSendEmailOne">
<h3>Valor Actual: {{ $subPage }}</h3>
<p class="m-0">Este valor se actualiza automáticamente cada 7 segundos.</p>
</div>
<label for="">SubPage</label>
@endifEl código completo para el envío automatizado de mi componente en Laravel Livewire:
app/Livewire/Dashboard/Blog/SubscribeSendEmail.php
<?php
namespace App\Livewire\Dashboard\Blog;
use App\Mail\SubscribeEmail;
use App\Models\Subscribe;
use App\Models\SubscriptionContent;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Support\Facades\Config;
class SubscribeSendEmail extends Component
{
// objeto
public SubscriptionContent $subscriptionContent;
// info
public $subscribes = [];
public $countSubscribes = 0;
// para enviar el correo, se activa el poll y si esta activo
public $subPage = 0; // sub actual
public $active = false; // enviando correos
***
// para pasar al siguiente sub a enviar el correo
public function startSendEmailOne()
{
$this->submit();
$this->subPage++;
}
// esta funcion inicia el proceso de enviar el correo individual
public function submit()
{
// activa el poll
$this->active = true;
// envia el primer correo
$this->sendEmailSubscriptions();
}
// envia el correo a cada sub
function sendEmailSubscriptions()
{
// ENVIAR A TODOS
$subscribe = Subscribe::select('name', 'email', 'id')
->where('active', $this->typeSubscriptors) // Grupo de Subs seleccionado
->offset($this->subPage) // pagina que va variando en el poll
->limit(1) // solo uno
->orderBy('id')->first();
// si no hay sub termina
if (!$subscribe) {
$this->active = false;
return;
}
// *** Testing
Log::info("$this->subPage - $subscribe->id $subscribe->email");
// correo electronico de la app
$smtpUser = config('mail')['mailers']['smtp']['username'];
// prepara el mail para enviar el correo
$mailer = Mail::build([
'transport' => 'smtp',
//'transport' => 'log', // *** PARA TESTING EN LOCAL
'host' => 'smtp.domain.com',
'port' => 465,
'encryption' => 'ssl',
'username' => $smtpUser,
'password' => config('mail')['mailers']['smtp']['password'],
]);
// Envio el correo
$mailer
->to($subscribe->email)
->send(
(new SubscribeEmail($subscribe->email, $subscribe->name ?? $subscribe->email, $this->subscriptionContent->title, $this->subscriptionContent->content))
->from($smtpUser, 'Andrés Cruz')
);
}
}Y el blade:
resources/views/livewire/dashboard/subscribe-send-email.blade.php
@if ($active)
<div wire:poll.7000ms.keep-alive="startSendEmailOne">
<h3>Valor Actual: {{ $subPage }}</h3>
<p class="m-0">Este valor se actualiza automáticamente cada 7 segundos.</p>
</div>
<label for="">SubPage</label>
@endif
<x-button class="mt-2 mb-3" wire:click="startSendEmailOne">
{{ __('Start Send Email') }}
</x-button>Optimización y Concurrencia
Si necesitas ir más rápido, puedes abrir varias instancias del panel. Por ejemplo:
- Pestaña 1: Envía desde el suscriptor 0 al 4,500.
- Pestaña 2: Envía desde el suscriptor 4,501 al 9,000.
Esto permite el envío concurrente y reduce el tiempo a la mitad. Recuerda que el intervalo de 7 segundos es para evitar el error 500; si envías correos cada medio segundo y el servicio de mail tarda 2 segundos en responder, saturarás la cola y el proceso fallará.
Alternativa con JavaScript
Si prefieres no usar Livewire, puedes lograr lo mismo con setInterval o setTimeout en JavaScript, realizando peticiones mediante AJAX o Fetch al servidor cada 7 segundos de forma cíclica.
El siguiente paso es que conozcas como puedes extender Laravel con funcionalidades claves como lo son, detectar si la navegación es mobile o por PC.