Mengirim email dari aplikasi dengan Email library dan SMTP, menyusun template email HTML dengan fallback teks, lalu membangun alur reset password via email yang aman dari generate token sampai update password.

Aplikasi web hampir selalu perlu mengirim email: verifikasi akun, reset password, notifikasi order, atau laporan. Di episode 11 ini kita menguasai Email library CodeIgniter 4 — dari konfigurasi SMTP, template email yang profesional, hingga praktik nyata reset password via email.
Mengapa email penting dikuasai? Karena email adalah salah satu kanal komunikasi paling sensitif: jika alur reset password bocor atau template menampilkan data user mentah, itu pintu masuk serangan. Dan seperti biasa, CodeIgniter memberi kita library lengkap sehingga fokusnya tinggal ke logika aplikasi.
Konfigurasi SMTP di app/Config/Email.php (atau override via .env):
email.protocol = smtp
email.SMTPHost = smtp.mailtrap.io
email.SMTPPort = 587
email.SMTPUser = username
email.SMTPPass = password
email.SMTPCrypto = tls
email.SMTPTimeout = 5
email.mailType = htmlNote
Untuk development, gunakan Mailtrap atau sejenisnya — email masuk ke kotak virtual dan tidak benar-benar terkirim. Ini menghindari spam inbox dan menahan pengiriman palsu. Untuk produksi, gunakan SMTP provider nyata seperti SES, SendGrid, atau SMTP hosting.
Field kunci di app/Config/Email.php:
| Property | Arti |
|---|---|
$protocol | smtp, sendmail, atau mail |
$SMTPHost | Host SMTP server |
$SMTPPort | Port (587 untuk submission, 465 untuk SSL) |
$SMTPCrypto | tls atau ssl |
$mailType | text atau html |
$email = \Config\Services::email();
$email->setFrom('noreply@devvnull.test', 'DevNull');
$email->setTo($user['email']);
$email->setSubject('Reset Password Akun DevNull');
$email->setMessage('Klik link berikut untuk reset password kalian...');
if ($email->send()) {
session()->setFlashdata('success', 'Email terkirim');
} else {
log_message('error', 'Email gagal: ' . $email->printDebugger(['headers']));
}\Config\Services::email() mengembalikan instance Email yang sudah memakai konfigurasi di atas. send() mengembalikan true/false, dan printDebugger(['headers']) menampilkan detail error SMTP untuk debugging.
Warning
Jangan asumsi send() berhasil tanpa mengecek return value. SMTP bisa gagal diam-diam: host unreachable, auth ditolak, atau rate limit. Selalu log error saat send() false agar masalah terdeteksi sejak dini.
Email profesional bukan satu paragraf plain text. Buat template di app/Views/emails/:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<style>
body { font-family: sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; }
.btn {
display: inline-block;
background: #2563eb;
color: #fff;
padding: 10px 20px;
text-decoration: none;
border-radius: 6px;
}
</style>
</head>
<body>
<div class="container">
<?= $this->renderSection('content') ?>
<hr>
<p><small>Email ini dikirim otomatis oleh DevNull. Jangan balas email ini.</small></p>
</div>
</body>
</html><?= $this->extend('emails/layout') ?>
<?= $this->section('content') ?>
<h2>Halo <?= esc($name) ?>,</h2>
<p>Kami menerima permintaan reset password untuk akun kalian.</p>
<p>Klik tombol di bawah untuk membuat password baru:</p>
<p><a class="btn" href="<?= $resetLink ?>">Reset Password</a></p>
<p>Link ini berlaku selama <strong>1 jam</strong>. Abaikan email ini jika kalian tidak meminta reset password.</p>
<?= $this->endSection() ?>Layout dan section di template email sama persis dengan yang kita pelajari di episode 5 — konsistensi yang membuat kalian tidak perlu belajar sintaks baru.
Alur reset password yang aman:
public function send(): \CodeIgniter\HTTP\RedirectResponse
{
$email = $this->request->getPost('email');
$user = (new UserModel())->where('email', $email)->first();
if ($user === null) {
// jangan ungkap apakah email terdaftar
return redirect()->back()->with('success', 'Jika email terdaftar, link reset terkirim.');
}
$token = bin2hex(random_bytes(32));
$db = \Config\Database::connect();
$db->table('password_resets')->insert([
'email' => $email,
'token_hash' => hash('sha256', $token),
'expires_at' => date('Y-m-d H:i:s', strtotime('+1 hour')),
]);
$emailService = \Config\Services::email();
$emailService->setFrom('noreply@devvnull.test', 'DevNull');
$emailService->setTo($email);
$emailService->setSubject('Reset Password DevNull');
$emailService->setMessage(view('emails/reset_password', [
'name' => $user['name'],
'resetLink' => site_url("reset-password/{$token}"),
]));
$emailService->send();
return redirect()->back()->with('success', 'Jika email terdaftar, link reset terkirim.');
}Poin keamanan penting: kita menyimpan hash token (hash('sha256', $token)), bukan token mentah — jika database bocor, penyerang tidak bisa memakai token tersebut. Dan pesan suksesnya identik untuk email terdaftar/tidak (anti user enumeration).
public function reset(string $token): string | \CodeIgniter\HTTP\RedirectResponse
{
$db = \Config\Database::connect();
$record = $db->table('password_resets')
->where('token_hash', hash('sha256', $token))
->where('expires_at >', date('Y-m-d H:i:s'))
->get()->getRowArray();
if ($record === null) {
return redirect()->to('/forgot-password')->with('error', 'Link reset tidak valid atau sudah kedaluwarsa.');
}
if ($this->request->getMethod() === 'post') {
$rules = [
'password' => 'required|min_length[8]',
'confirm_password' => 'required|matches[password]',
];
if (! $this->validate($rules)) {
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
}
(new UserModel())->where('email', $record['email'])
->set(['password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT)])
->update();
$db->table('password_resets')->where('email', $record['email'])->delete();
return redirect()->to('/login')->with('success', 'Password berhasil diubah. Silakan login.');
}
return view('auth/reset_password');
}Setelah password diubah, semua token reset user tersebut dihapus — mencegah pemakaian ulang token lama.
Warning
Jangan pernah mengirim token di dalam query parameter tanpa hash di database, dan jangan biarkan link reset berlaku selamanya. Token sekali pakai + expiry + hash penyimpanan adalah kombinasi minimum yang aman.
Email.php/.env; gunakan Mailtrap untuk development.\Config\Services::email() + setFrom/setTo/setSubject/setMessage lalu send().Inti yang harus dibawa pulang:
send() dan log kegagalan SMTP.Di episode 12 selanjutnya kita akan membangun authentication & authorization — auth session-based custom, lalu naik ke CodeIgniter Shield sebagai package resmi, plus role-based access dengan filter untuk admin/user. Ini salah satu episode terpenting di series ini!