To send an email with an attachment in Laravel 13, return an array of Attachment objects from the attachments() method of your Mailable. Use Attachment::fromPath() for a file on disk, Attachment::fromStorage() for a file on a filesystem disk, and Attachment::fromData() for content generated in memory such as a PDF.
This tutorial targets Laravel 13 (PHP 8.3 to 8.5). The code was written for this article and not executed in a live project, so try it with the log mailer first.
What do you need before you start?
- A Laravel 13 application on PHP 8.3 or newer.
- Mail settings in
.env. For local testing,MAIL_MAILER=logwrites the full raw email, including the base64 attachment, tostorage/logs/laravel.log. A tool like Mailpit shows attachments in a real inbox UI. - A file to attach, for example
storage/app/private/reports/invoice.pdf.
Step 1: How do you create the Mailable?
php artisan make:mail InvoiceMailThis creates app/Mail/InvoiceMail.php with envelope(), content() and attachments() methods.
Step 2: How do you attach files in attachments()?
Here is a Mailable that attaches a stored file and a file generated in memory.
namespace App\Mail;
use App\Models\Order;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class InvoiceMail extends Mailable
{
public function __construct(public Order $order)
{
}
public function envelope(): Envelope
{
return new Envelope(subject: 'Your invoice #' . $this->order->id);
}
public function content(): Content
{
return new Content(view: 'emails.invoice');
}
public function attachments(): array
{
return [
Attachment::fromStorage('reports/invoice-' . $this->order->id . '.pdf')
->as('invoice.pdf')
->withMime('application/pdf'),
Attachment::fromData(
fn () => $this->order->toCsv(),
'order.csv'
)->withMime('text/csv'),
];
}
}toCsv() is a placeholder for your own method that returns a string. fromStorage() reads from the default disk; use fromStorageDisk('s3', 'path') for another disk. as() sets the filename the recipient sees and withMime() sets the content type.
How do you attach a file from a path?
Use Attachment::fromPath(public_path('files/terms.pdf')) when the file lives outside a configured disk. Always build the path yourself; never concatenate user input into it, or an attacker could request any file the server can read.
Step 3: How do you create the email view?
Create resources/views/emails/invoice.blade.php.
<p>Hello {{ $order->customer_name }},</p>
<p>Your invoice for order #{{ $order->id }} is attached.</p>Public properties on the Mailable, like $order, are available in the view automatically.
Step 4: How do you send it?
Call it from a controller, command or job.
use App\Mail\InvoiceMail;
use App\Models\Order;
use Illuminate\Support\Facades\Mail;
Route::post('/orders/{order}/send-invoice', function (Order $order) {
Mail::to($order->customer_email)->send(new InvoiceMail($order));
return back()->with('status', 'Invoice sent.');
});For slow attachments such as generated PDFs, implement ShouldQueue on the Mailable or call Mail::to(...)->queue(...) so the web request does not wait for the mail server.
Can you attach an uploaded file?
Yes. Pass the uploaded file to the Mailable and attach it with Attachment::fromPath($file->getRealPath()). For queued mail, store the upload first and attach it from storage, because a temporary upload path may not exist when the worker runs. Validate the upload type and size before sending.
$request->validate([
'document' => ['required', 'file', 'mimes:pdf,docx', 'max:5120'],
]);
$path = $request->file('document')->store('mail-attachments');Common mistakes
- Wrong disk path.
fromStorage()uses the default disk, which islocal(storage/app/private) in new Laravel apps, notpublic. - Queued mail with temporary files. Temporary upload paths vanish before a worker picks up the job.
- Huge attachments. Many providers reject messages over 20 to 25 MB. Send a signed download link for big files instead.
- Using the old
attach()style. Older tutorials call$this->attach()insidebuild(); theattachments()method is the current approach. - Missing MIME type for generated data. Without
withMime(), some clients cannot open the file.
How do you verify it works?
- Set
MAIL_MAILER=logand send the mail. - Search
storage/logs/laravel.logforContent-Disposition: attachment; filename=invoice.pdf. - Or use Mailpit and confirm the attachment appears and opens.
- Write a test with
Mail::fake()and call$mailable->assertHasAttachment(...)insideMail::assertSent().
Mail::fake();
Mail::to('a@example.com')->send(new InvoiceMail($order));
Mail::assertSent(InvoiceMail::class, function ($mail) {
return $mail->hasTo('a@example.com');
});FAQ
Can I attach several files?
Yes. Return as many Attachment objects as you need in the array.
Does it work with notifications?
Yes. On a MailMessage, use ->attach(), ->attachFromStorage() or ->attachData().
How do I embed an inline image?
Use $message->embed($path) in a Blade mail view instead of an attachment.



