Mail: Kirim Email dengan Mailable
Kirim email transaksional: welcome, reset password, notifikasi.
Kenapa Mailable, Bukan Mail::raw
Email transaksional (welcome, reset password, invoice, OTP) adalah wajah profesional aplikasimu. Menulis HTML email langsung di controller dengan Mail::raw() memang cepat, tapi tiga bulan kemudian kamu punya 15 template email berceceran, tidak bisa di-test, dan tidak bisa di-queue dengan rapi. Mailable membungkus satu email menjadi satu class: subject, view, attachment, dan data, semuanya di satu tempat yang bisa di-test.
Anatomi Mailable Modern
Laravel 9+ memakai struktur envelope() (kepala surat) dan content() (isi). Generate dengan artisan:
php artisan make:mail InvoiceMail// app/Mail/InvoiceMail.php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class InvoiceMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public string $nomorInvoice,
public int $total,
public string $pdfPath, // path di storage
) {}
public function envelope(): Envelope
{
return new Envelope(
from: '[email protected]',
subject: 'Invoice '.$this->nomorInvoice.' sudah terbit',
);
}
public function content(): Content
{
return new Content(
view: 'emails.invoice',
with: ['totalRp' => number_format($this->total, 0, ',', '.')],
);
}
public function attachments(): array
{
return [
Attachment::fromStorage($this->pdfPath)
->as('invoice-'.$this->nomorInvoice.'.pdf')
->withMime('application/pdf'),
];
}
}Properti public di constructor otomatis tersedia di Blade view, jadi tidak perlu with() manual kecuali untuk olahan data.
{{-- resources/views/emails/invoice.blade.php --}}
<h1>Terima kasih sudah berbelanja!</h1>
<p>Invoice <strong>{{ $nomorInvoice }}</strong> sebesar Rp{{ $totalRp }} terlampir.</p>Mengirimnya satu baris:
use App\Mail\InvoiceMail;
use Illuminate\Support\Facades\Mail;
Mail::to($user->email)->send(new InvoiceMail('INV-001', 150000, 'invoices/INV-001.pdf'));Konfigurasi Mailer
Di .env, pilih driver sesuai kebutuhan:
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=...
MAIL_PASSWORD=...
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="TokoMu"Untuk development, MAIL_MAILER=log mencetak isi email ke storage/logs/laravel.log, jadi kamu bisa verifikasi template tanpa benar-benar mengirim. Mailtrap memberi inbox dummy yang bisa dilihat di browser.
Testing Email
Jangan kirim email beneran di test. Laravel menyediakan fake:
// tests/Feature/CheckoutTest.php
use App\Mail\InvoiceMail;
use Illuminate\Support\Facades\Mail;
public function test_checkout_mengirim_invoice(): void
{
Mail::fake();
$this->actingAs($user)->post('/checkout', [...]);
Mail::assertSent(InvoiceMail::class, function ($mail) use ($user) {
return $mail->hasTo($user->email)
&& $mail->nomorInvoice === 'INV-001';
});
}Mail::fake() mencegat semua pengiriman, lalu assertSent memastikan email yang tepat terkirim ke orang yang tepat. Ini jauh lebih meyakinkan daripada mengecek log manual.
Jebakan Umum
Kirim email sinkron di dalam loop. Mengirim 500 invoice satu per satu di request HTTP berarti user menunggu menit-menit, dan kalau request timeout di tengah, sebagian user dapat email sebagian tidak. Solusinya: Mail::to(...)->queue(...) atau implementasikan ShouldQueue di Mailable, lalu biarkan worker yang mengerjakan (modul queue).
Lupa SerializesModels. Tanpa trait ini, model Eloquent yang dikirim ke Mailable di-queue akan di-serialize utuh (termasuk semua atribut). Dengan trait ini, hanya ID yang disimpan dan model di-fetch ulang saat job jalan, jadi datanya selalu fresh.
Hardcode alamat from di setiap Mailable. Set MAIL_FROM_ADDRESS sekali di .env, override hanya untuk kasus khusus seperti email balasan support.
Catatan teknis: Butuh preview cepat tanpa kirim? Return Mailable langsung dari route:
Route::get('/preview-invoice', fn () => new InvoiceMail('INV-001', 150000, 'x.pdf'));. Laravel me-render HTML email di browser. Ingat ini hanya untuk development, jangan biarkan route preview lolos ke production karena bisa membocorkan data contoh.
Tantangan
Email Selamat Datang
Buat WelcomeMail + view. Kirim ke dirimu sendiri via tinker dengan MAIL_MAILER=log, lalu cek storage/logs/laravel.log untuk melihat isi emailnya.