Surel di Laravel dikirim melalui kelas yang disebut mailable, yaitu objek yang membungkus isi surat, alamat pengirim dan penerima, serta templat penampilnya. Topik ini membahas struktur mailable pada aplikasi catatan, templat markdown yang membentuk tampilan surat, cara mengirimnya dengan mailer log, dan cara mengujinya dengan Mail::fake(). Setelah membacanya, pengiriman ringkasan catatan dapat dibangun tanpa server SMTP sama sekali, baik pada saat pengembangan maupun pengujian.
Mailer dan alamat pengirim#
Mailer adalah konfigurasi cara surat dikirim, didefinisikan pada berkas config/mail.php dan dipilih melalui variabel MAIL_MAILER pada berkas .env. Proyek aplikasi catatan menggunakan mailer log, yang tidak mengirim apa pun ke jaringan, melainkan menuliskan seluruh pesan surat ke berkas storage/logs/laravel.log, sehingga hasil pengiriman dapat diperiksa secara nyata tanpa server surat.
| Mailer | Perilaku |
|---|---|
log | Pesan ditulis ke berkas log aplikasi |
array | Pesan disimpan dalam memori, khusus untuk pengujian |
smtp | Pesan dikirim ke server SMTP pada MAIL_HOST |
failover | Menyusuri daftar mailer lain sampai satu pengiriman berhasil |
Alamat pengirim tidak diatur per surat, melainkan berasal dari konfigurasi global MAIL_FROM_ADDRESS dan MAIL_FROM_NAME, sedangkan mailable hanya mengatur subjek serta isinya. Apabila sebuah mailable perlu menggunakan alamat pengirim berbeda, properti from dapat dideklarasikan pada kelasnya, dan nilai itu akan menimpa konfigurasi global untuk surat tersebut.
Membuat mailable#
Mailable dibuat dengan perintah make:mail. Opsi --markdown sekaligus membuat templat Blade markdown yang digunakan sebagai isi surat, sehingga satu perintah menghasilkan dua berkas.
# tidak-dijalankan
# membuat mailable beserta templat markdown-nya
cd ~/aplikasi-catatan
php artisan make:mail RingkasanCatatan --markdown=mail.ringkasan-catatanPerintah tersebut tidak dieksekusi ulang pada contoh ini karena kedua berkasnya sudah dibuat. Keluaran asli dari perintah itu adalah sebagai berikut.
INFO Mailable [app/Mail/RingkasanCatatan.php] created successfully.
INFO Markdown view [resources/views/mail/ringkasan-catatan.blade.php] created successfully. Kelas hasil perintah tersebut disunting agar menerima satu model Note, mengatur subjek pada envelope(), dan meneruskan datanya ke templat melalui content().
// tidak-dijalankan
<?php
namespace App\Mail;
use App\Models\Note;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class RingkasanCatatan extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(public Note $note)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Ringkasan catatan',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'mail.ringkasan-catatan',
with: ['catatan' => $this->note],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}Kelas mailable membagi pesan menjadi tiga definisi. Metode envelope() menangani sampul pesan seperti subjek dan alamat balasan, metode content() menunjuk templatnya beserta data yang diteruskan melalui with, dan metode attachments() mendaftar lampiran yang dalam contoh ini masih kosong. Trait Queueable, SerializesModels pada kelasnya membuat mailable ini juga dapat dikirim melalui antrian, sehingga pengiriman yang berat tidak membebani permintaan web.
Templat markdown surat#
Templat markdown memisahkan isi surat dari tampilannya. Komponen x-mail::message membungkus seluruh isi, judul ditulis seperti heading markdown, dan komponen x-mail::button menghasilkan tombol tautan yang tampilannya sudah disetel oleh Laravel. Templat yang digunakan mailable RingkasanCatatan adalah sebagai berikut.
<x-mail::message>
# Ringkasan catatan
Catatan **{{ $catatan->judul }}** berisi ringkasan berikut.
{{ $catatan->isi }}
<x-mail::button :url="url('/catatan/'.$catatan->id)">
Buka catatan
</x-mail::button>
Terima kasih,<br>
{{ config('app.name') }}
</x-mail::message>Laravel merender templat ini menjadi dua versi sekaligus, yaitu versi teks polos untuk klien surel yang tidak menampilkan HTML dan versi HTML ber-CSS untuk klien yang lain, sehingga keduanya tidak perlu ditulis terpisah. Bagian penutup surat berisi nama aplikasi dari config('app.name'), dan baris hak cipta disisipkan secara otomatis oleh komponen x-mail::message. Untuk isi berbentuk tabel, komponen x-mail::table menyediakan tabel dengan gaya yang seragam, konsisten dengan tombol dan komponen lainnya.
Mengirim surat#
Pengiriman dilakukan melalui facade Mail, yaitu memanggil Mail::to() dengan alamat penerima lalu metode send() dengan instance mailable. Penerima dapat berupa satu alamat atau larik alamat, dan metode cc() serta bcc() menambahkan penerima salinan dengan bentuk pemanggilan yang sama.
// tidak-dijalankan
use App\Mail\RingkasanCatatan;
use Illuminate\Support\Facades\Mail;
Mail::to('[email protected]')->send(new RingkasanCatatan($catatan));Contoh berikut mengirim surat ringkasan dari Tinker, dengan log mailer dikosongkan lebih dahulu agar baris yang diperiksa selalu milik pengiriman ini.
# mengosongkan log mailer lalu mengirim surat ringkasan
cd ~/aplikasi-catatan
: > storage/logs/laravel.log
php artisan tinker --execute='
use App\Mail\RingkasanCatatan;
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\Mail;
$kerja = Category::firstOrCreate(["nama" => "Kerja"]);
$catatan = Note::firstOrCreate(
["judul" => "Pengingat arsip bulanan"],
["isi" => "Menandai catatan yang perlu diarsipkan pada akhir bulan.", "category_id" => $kerja->id]
);
Mail::to("[email protected]")->send(new RingkasanCatatan($catatan));
echo "Surat ditulis ke log mailer." . PHP_EOL;
'
grep -E "(From|To|Subject):" storage/logs/laravel.log | sed -E "s/^\[[^]]+\] local\.DEBUG: //"
grep -E "^Catatan \*\*" storage/logs/laravel.logSurat ditulis ke log mailer.
From: Laravel <[email protected]>
To: [email protected]
Subject: Ringkasan catatan
Catatan **Pengingat arsip bulanan** berisi ringkasan berikut.Baris header memperlihatkan alamat pengirim global [email protected] dengan nama aplikasi, alamat penerima dari Mail::to(), dan subjek dari envelope(), sedangkan baris terakhir berasal dari isi templat yang sudah merender data catatannya. Pesan lengkap beserta versi HTML-nya tersimpan utuh pada berkas log, sehingga dapat dibaca lebih rinci apabila diperlukan.
Pengujian pengiriman surat#
Pengujian tidak membaca berkas log, melainkan memalsukan mailer dengan Mail::fake(). Setelah pemanggilan ini, semua surat ditangkap di memori tanpa dikirim, kemudian asersi memeriksa mailable mana yang terkirim beserta penerimanya.
// tidak-dijalankan
<?php
namespace Tests\Feature;
use App\Mail\RingkasanCatatan;
use App\Models\Note;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class PengirimanSuratTest extends TestCase
{
use RefreshDatabase;
public function test_surat_ringkasan_terkirim_ke_alamat_tujuan(): void
{
Mail::fake();
$catatan = Note::factory()->create(['judul' => 'Pengingat arsip bulanan']);
Mail::to('[email protected]')->send(new RingkasanCatatan($catatan));
Mail::assertSent(RingkasanCatatan::class, function (RingkasanCatatan $surat) use ($catatan) {
return $surat->hasTo('[email protected]') && $surat->note->id === $catatan->id;
});
}
}Metode assertSent() menerima nama kelas mailable dan fungsi pemeriksa yang dapat menguji isi surat, termasuk penerimanya melalui hasTo(). Pasangan asersi yang berlawanan juga tersedia, yaitu assertNotSent() untuk memastikan surat tertentu tidak dikirim dan assertNothingSent() untuk memastikan tidak ada surat sama sekali. Pengujian dijalankan dengan php artisan test, dan baris statusnya disaring agar keluaran yang ditampilkan tetap stabil.
# menjalankan pengujian fitur pengiriman surat
cd ~/aplikasi-catatan
php artisan test --filter=PengirimanSuratTest | grep -E "PASS|Tests:" PASS Tests\Feature\PengirimanSuratTest
Tests: 1 passed (1 assertions)Keluaran tersebut memperlihatkan satu pengujian lulus dengan satu asersi, yaitu surat ringkasan terkirim kepada alamat yang diharapkan. Karena mailer dipalsukan, pengujian ini berjalan cepat dan dapat diulang kapan pun tanpa menyentuh jaringan maupun log aplikasi.
Kesalahan umum#
Kesalahan pertama adalah meneruskan data templat tanpa argumen with pada content(). Templat yang membaca $catatan akan gagal dengan pesan Undefined variable $catatan ketika datanya tidak diteruskan, dan perbaikannya adalah menambahkan with: ['catatan' => $this->note] pada definisi kontennya.
Kesalahan kedua adalah memeriksa log mailer tanpa mengosongkannya terlebih dahulu. Berkas log menampung seluruh pesan sejak aplikasi berjalan, sehingga hasil pencarian dapat saja menemukan surat-surat lama dari percobaan sebelumnya. Kosongkan berkasnya sebelum mengirim, atau periksa baris terakhirnya, apabila hasil yang dituju adalah pengiriman terakhir.
Kesalahan ketiga adalah menjalankan pengujian yang memanggil Mail::to()->send() tanpa Mail::fake(). Pada mailer log, akibatnya hanya berupa berkas log yang menumpuk, tetapi pada konfigurasi yang menggunakan mailer smtp, pengujian yang gagal dapat mengirim surat sungguhan ke alamat yang terdaftar. Panggil Mail::fake() di awal setiap pengujian yang melibatkan pengiriman surel.
Ringkasan#
- Mailable membungkus satu pesan surat, dengan sampul pada
envelope(), templat beserta data padacontent(), dan lampiran padaattachments(). - Templat markdown menggunakan komponen
x-mail::messagedanx-mail::button, lalu dirender menjadi versi teks dan versi HTML sekaligus. - Mailer log menuliskan seluruh pesan ke
storage/logs/laravel.log, sehingga pengiriman dapat diperiksa tanpa server SMTP. Mail::fake()menangkap surat pada memori danMail::assertSent()memverifikasi mailable yang terkirim beserta penerimanya.
Referensi#
- Mail — Laravel — Dokumentasi resmi mailable, templat markdown, mailer, dan pengiriman surel.
- Mocking — Laravel — Dokumentasi facade palsu pada pengujian, termasuk faking facade Mail dan asersi pengirimannya.