Every one of Laravel's mail-sending scenarios — a generic SMTP server, Gmail specifically, a transactional service like SendGrid, sending to multiple recipients, or attaching a file — goes through the same Mailable class structure, differing mainly in .env configuration.
Creating a Mailable
php artisan make:mail OrderConfirmation
class OrderConfirmation extends Mailable
{
public function __construct(public Order $order) {}
public function build()
{
return $this->subject('Your Order Confirmation')
->view('emails.order-confirmation')
->with(['order' => $this->order]);
}
}
Sending it
Mail::to($order->customer_email)->send(new OrderConfirmation($order));
Generic SMTP configuration
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your-username
MAIL_PASSWORD=your-password
MAIL_ENCRYPTION=tls
Gmail SMTP configuration specifically
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-password
MAIL_ENCRYPTION=tls
Gmail specifically requires an "app password" rather than the actual account password once 2-factor authentication is enabled (which Google effectively requires for this use case) — generated separately in the Google Account security settings, not the regular login password.
SendGrid configuration (via SMTP relay)
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD=your-sendgrid-api-key
MAIL_ENCRYPTION=tls
The literal string apikey as the username is correct and intentional for SendGrid's SMTP relay — this isn't a placeholder to replace, it's the actual expected value, with the real SendGrid API key going in the password field instead.
Sending to multiple recipients, with CC
Mail::to($primaryEmail)
->cc(['manager@example.com', 'accounting@example.com'])
->send(new OrderConfirmation($order));
// Sending the same email to many recipients individually (not all in one To: header)
foreach ($subscribers as $subscriber) {
Mail::to($subscriber->email)->send(new NewsletterMail($content));
}
Looping and calling Mail::to() individually per recipient (rather than passing an array to a single to() call) keeps each recipient's email address private from the others — passing multiple addresses to one to() call puts them all in the same visible To: header.
Attaching a file
public function build()
{
return $this->subject('Your Invoice')
->view('emails.invoice')
->attach(storage_path('app/invoices/invoice-1001.pdf'), [
'as' => 'invoice.pdf',
'mime' => 'application/pdf',
]);
}
Queueing emails instead of sending synchronously
class OrderConfirmation extends Mailable implements ShouldQueue
{
// ...
}
Implementing ShouldQueue on the Mailable defers the actual sending to a background queue worker rather than blocking the current request — worth doing for any email sent as part of a user-facing request (like this order confirmation), so a slow or temporarily unreachable mail server doesn't delay the response the user is actively waiting for.