Content Index
- Mailable Class
- Sending individual emails
- Parameters: to, cc, and bcc
- CC and BCC
- Sending bulk emails
- Sending user email verification messages in Laravel
- Sending confirmation messages programmatically
- Extra: REST API to register and verify users
- GREAT TRICK to send automated and bulk emails in Laravel with Livewire
- Implementation
- 1. Managing the counter (subPage)
- 2. The send function (sendEmailSubscriptions)
- 3. The secret: wire:poll and the .keep-alive modifier
- Optimization and concurrency
- JavaScript Alternative
Once you know how to limit the number of user requests, it is natural to take the next step: learning how to send emails to notify those same users. In Laravel, configuring email sending is surprisingly simple. If you already have hosting with a mail server, all you need to do is create an address with its corresponding password.
From there, you just need to adjust a few parameters in your project. If you don't know them because the mail server is provided by a third party, you will need to check with your provider. In the case of Hostinger, the configuration in config/mail.php would look like this:
'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,
],If you don't have access to a real mail server during development, I recommend using a testing service like Mailtrap. It is completely free: you just need to create an account, create an inbox, and copy the credentials into your project. That way, you can test email sending without the risk of sending real emails to anyone.
The configuration in your .env file with Mailtrap would be the following:
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=ec5cbede982042
MAIL_PASSWORD=********0be7Mailable Class
To send any email in Laravel, you must create a Mailable class. Just like with models, controllers, or form requests, this class follows a specific structure that the framework understands and manages. You can generate it automatically with the following Artisan command:
$ php artisan make:mail OrderShippedThe resulting file, app/Mail/OrderShipped.php, will have this base structure:
<?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 [];
}
}Each method serves a specific function. The envelope() method defines the subject of the email; the content() method specifies the Blade view that will be used as the message body; and the attachments() method allows attaching files. The constructor, as usual in PHP, is responsible for initializing the class properties when creating the object—in this case, the recipient's email, the title, and the content.
The public properties you define in the class ($email, $title, $content) will automatically be available in the corresponding Blade view, without needing to pass them explicitly. You can add or modify these properties according to the needs of your project.
There is also a more compact alternative approach using the build() method, which allows you to define the subject and the view in a single place. This pattern was very common before Laravel 9 and you will still find it in many projects:
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');
}
}From the class, you can customize all the arguments the email will receive—subject, recipient, content—and return a Blade view representing the email body. For example, a minimal view in resources/views/emails/subscribe.blade.php could be:
<p>Hi<br>
{!! $content !!}The {!! !!} operator renders unescaped HTML, which is useful when the email content comes from a rich text editor like CKEditor. Use it with caution and always ensure the source of the content is trusted to prevent XSS injections.
Sending individual emails
To send an email, you use Laravel's Mail facade, which exposes a fluent and highly readable API. Simply instantiate your Mailable class and pass it to the send() method:
Mail::to('no-reply@example.net.com')->send(new SubscribeEmail('contact@gmail.com', $title, $content));Parameters: to, cc, and bcc
You are not limited to a single recipient. Laravel allows you to chain the to(), cc(), and bcc() methods to precisely configure who receives the email:
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new SubscribeEmail('contact@gmail.com', $title, $content));CC and BCC
The cc (carbon copy) allows sending a copy of the email to other people, and all recipients can see who else received it. The bcc (blind carbon copy) works the same way, but the recipient list remains invisible to the rest: no one knows who else the message was sent to. This distinction is key when sending bulk emails.
Sending bulk emails
A common way to send emails to multiple recipients simultaneously is to pass an array of emails to the cc() method. However, this approach has a major drawback: it exposes all addresses to all recipients, which creates distrust and may violate your users' privacy:
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));The solution is simple: use bcc() instead of cc(). This way, recipients remain hidden and no one can see the other addresses on the list:
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));As seen in the image, the emails defined in bcc do not appear visible in the message header:
Instead of appearing like in the example with cc, the recipients are simply not shown. A good practice for any bulk mailing.
Sending user email verification messages in Laravel
Confirmation emails are a fundamental pillar in any web system with user registration: they are sent after completing an action to confirm that the operation was successful (password change, product purchase, subscription sign-up, etc.). Laravel makes this very easy to implement natively.
To enable email verification, the first step is to implement the MustVerifyEmail contract in your user model:
use Illuminate\Contracts\Auth\MustVerifyEmail;
***
class User extends Authenticatable implements MustVerifyEmailWith that in place, you now have access to the built-in verification mechanisms in the framework.
Sending confirmation messages programmatically
From any instance of an authenticated user, you can trigger the sending of the verification email with a single method:
$user->sendEmailVerificationNotification();Of course, for this to work, you must have your SMTP server properly configured in the .env file. When executing this method, the user will receive an email similar to the following:

Extra: REST API to register and verify users
Here is a real and very common use case: a REST API to register and verify users. We have two distinct functions: one for registration, which automatically triggers the verification email upon completion; and another to resend verification programmatically:
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");
}As you can see, in the register() function we retrieve the user data, create the user, log in, and call sendEmailVerificationNotification() to trigger the verification email automatically. Notice that calling Auth::attempt() before sending is important: some verification mechanisms require the user to be authenticated in order to generate the signed link correctly.
The verifie() function is an independent endpoint that you can call programmatically whenever you need to resend the verification email without going through registration again. In this example, we use Laravel Sanctum with tokens, but the same approach works with any authentication guard.
GREAT TRICK to send automated and bulk emails in Laravel with Livewire
I am going to show you how to send emails automatically every 7, 5, or 4 seconds—or whatever interval you choose—using Laravel Livewire. Every time the counter advances, an email is sent to a different subscriber. It is a simple yet surprisingly efficient system.
I have used it myself to send around 1,000 emails in less than two hours. If you have a database of, say, 9,000 users, you can estimate the time required or even parallelize the process by opening multiple browser tabs at the same time. If you don't use Livewire, don't worry: at the end of the article, I'll explain how to replicate this same setup with a bit of JavaScript.
The panel I use to manage send-outs composes the email body in HTML thanks to the CKEditor plugin. If you're interested in integrating it with Laravel, I have several videos and articles about it on the blog.
Implementation
The core of this solution is a Livewire component that leverages its reactive nature. Here are the key points:
1. Managing the counter (subPage)
I use a property called $subPage that starts at zero. This property is essential for process resilience: if an error occurs (the internet drops or the server fails), I can see which block the last send belonged to and resume from that exact point. This allows me, for instance, to send blocks of 300 emails, let the server rest for a few minutes, and continue later without losing track.
<?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. The send function (sendEmailSubscriptions)
The query logic is very deliberate: I search for a single subscriber using limit(1)->first() with an offset() that increments with each cycle. As long as there are records, the process continues; as soon as the query returns null, it stops automatically:
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. The secret: wire:poll and the .keep-alive modifier
The real "magic" of this scheme is Livewire's wire:poll directive. This instruction tells the component how often to automatically refresh itself, calling a server function periodically without any user action required.
- Interval: I use 7 seconds (
7000ms). I have tested 3 or 4 seconds and it works fine, but 7 seconds is a safe margin to avoid overloading third-party services like Hostinger or Gmail and prevent timeout errors. - Keep-alive mode: The
.keep-alivemodifier (formerly called.alwaysin earlier versions of Livewire) ensures that the process continues even if you switch browser tabs. Without it, polling would stop when losing focus.
The component's Blade view with active polling:
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>
@endifThe complete code for the Livewire component for automated sending:
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')
);
}
}And the complete Blade view for the component:
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>Optimization and concurrency
If you need to speed up the process, you can open multiple instances of the dashboard in different tabs and assign a different subscriber range to each using $subPage. For example:
- Tab 1: Sends from subscriber 0 to 4,500.
- Tab 2: Sends from subscriber 4,501 to 9,000.
This allows concurrent sending and cuts the total time in half. An important note: the 7-second interval is not arbitrary. If you reduce the interval too much—for example, to half a second—and the mail server takes 2 seconds to respond, requests will queue up faster than they can be processed, eventually overwhelming the queue and causing 500 errors and lost sends.
JavaScript Alternative
If you prefer not to rely on Livewire, you can replicate this exact behavior using setInterval() or setTimeout() in JavaScript. The concept is the same: every X seconds, you make a fetch() or AJAX request to a Laravel endpoint that processes and sends the next email on the list. It is slightly more verbose, but equally effective.
The next step is for you to learn how to extend Laravel with key features like detecting whether navigation is mobile or desktop.