---
model_tier: medium
name: laravel-mail
description: "Use when building Laravel emails — Mailables, Markdown templates, queued sending, attachments, previews — even when the user says 'send this as an email' without naming Mailables."
domain: engineering
workspaces:
  - engineering
packs:
  - laravel
trust:
  level: professional
install:
  default: false
  removable: true
---

# laravel-mail

## When to use

Use this skill when building email functionality:
- Mailable classes with HTML/Blade or Markdown templates
- Queued email sending
- Attachments and inline images
- Mail testing and previewing

For **simple notification emails** (one-off messages), see [laravel-notifications](../laravel-notifications/SKILL.md).
Use Mailables when you need full control over the email template.

## Procedure: Create a Mailable

1. **Inspect existing mailables** — Review `app/Mail/` for naming, base class, queueing convention, and the templates in `resources/views/emails/` for the project's markdown style.
2. **Generate class** — `php artisan make:mail InvoiceMail --markdown=emails.invoice`.
3. **Configure** — Set subject, from, attachments, queuing (`ShouldQueue`).
4. **Create template** — Markdown template in `resources/views/emails/`.
5. **Verify** — Send test email, confirm rendering and delivery.

### Example

```bash
php artisan make:mail InvoiceMail --markdown=emails.invoice
```

```php
declare(strict_types=1);

namespace App\Mail;

use App\Models\Invoice;
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 InvoiceMail extends Mailable implements ShouldQueue
{
    use Queueable;
    use SerializesModels;

    public function __construct(
        private readonly Invoice $invoice,
    ) {}

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'Invoice #' . $this->invoice->getNumber(),
            replyTo: ['billing@example.com'],
        );
    }

    public function content(): Content
    {
        return new Content(
            markdown: 'emails.invoice',
            with: [
                'invoice' => $this->invoice,
                'url' => route('invoices.show', $this->invoice->getId()),
            ],
        );
    }

    /** @return array<int, \Illuminate\Mail\Mailables\Attachment> */
    public function attachments(): array
    {
        return [
            Attachment::fromPath('/path/to/invoice.pdf')
                ->as('invoice-' . $this->invoice->getNumber() . '.pdf')
                ->withMime('application/pdf'),
        ];
    }
}
```

## Markdown templates

```blade
{{-- resources/views/emails/invoice.blade.php --}}
<x-mail::message>
# Invoice {{ $invoice->getNumber() }}

Thank you for your order. Here is your invoice summary:

<x-mail::table>
| Item | Amount |
|:-----|-------:|
@foreach ($invoice->getItems() as $item)
| {{ $item->getName() }} | {{ $item->getFormattedAmount() }} |
@endforeach
| **Total** | **{{ $invoice->getFormattedTotal() }}** |
</x-mail::table>

<x-mail::button :url="$url">
View Invoice
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
```

## Sending mail

```php
// Send immediately
Mail::to($user)->send(new InvoiceMail($invoice));

// Queue for background sending (preferred)
Mail::to($user)->queue(new InvoiceMail($invoice));

// Send later
Mail::to($user)->later(now()->addMinutes(10), new InvoiceMail($invoice));

// Multiple recipients
Mail::to($users)
    ->cc($manager)
    ->bcc('archive@example.com')
    ->send(new InvoiceMail($invoice));
```

## Testing

```php
// Assert mail was sent
Mail::fake();

// ... trigger action ...

Mail::assertSent(InvoiceMail::class, function (InvoiceMail $mail) use ($user) {
    return $mail->hasTo($user->getEmail());
});

Mail::assertNotSent(InvoiceMail::class);
Mail::assertNothingSent();
Mail::assertQueued(InvoiceMail::class);
```

## Previewing in browser

```php
// routes/web.php (local only)
Route::get('/mail-preview', function () {
    $invoice = Invoice::factory()->create();
    return new InvoiceMail($invoice);
});
```

## Core rules

- **Always queue emails** — implement `ShouldQueue` to avoid blocking requests.
- **Use Markdown templates** for consistent styling across email clients.
- **Use Envelope + Content** pattern (Laravel 11+) — not the old `build()` method.
- **Test with `Mail::fake()`** — verify recipients, content, and queuing.
- **Keep Mailables focused** — one Mailable per email type.

## Output format

1. Mailable class with envelope, content, and attachments
2. Blade/Markdown email template
3. Queued mail dispatch integration

## Auto-trigger keywords

- Mailable
- email template
- send mail
- Mail::to
- markdown email
- mail attachment

## Gotcha

- Always queue emails (`ShouldQueue`) — synchronous sending blocks the request.
- The model forgets that mail templates are Blade files — they need to be published/created.
- Don't test email content with `Mail::fake()` alone — it doesn't render the template. Use `Mail::assertSent()` with closure.

## Do NOT

- Do NOT send emails synchronously in request lifecycle — always queue.
- Do NOT use `build()` method — use `envelope()`, `content()`, `attachments()`.
- Do NOT hardcode email addresses — use config or environment variables.
- Do NOT put HTML in Mailable classes — use Blade templates.
