Email template — html email, handlebars
Muammodan boshlaylik
Oldingi darsda emailni HTML satr sifatida yozdik:
html: `<h2>Salom ${ism}!</h2><p>Buyurtmangiz qabul qilindi.</p>`
Bu bir-ikki qator uchun yaxshi. Lekin real loyihada email murakkab bo'ladi: logo, tugmalar, jadvallar, rang sxemasi. Bu HTML ni JavaScript satr ichida yozish — dahshat.
Bundan tashqari, dizayner HTML ni o'zgartirmoqchi. U kod ichiga kirmaydi. Alohida fayl bo'lsa — o'zi o'zgartirishi mumkin.
Template — aynan shu muammoni hal qiladi.
Template nima?
Template — o'zgaruvchilar joylari belgilangan HTML fayl. Render paytida o'zgaruvchilar haqiqiy qiymatlar bilan almashtiriladi.
template.hbs: render qilingan:
────────────────────── ──────────────────────
Salom, {{ism}}! → Salom, Ali!
Buyurtma: {{raqam}} → Buyurtma: #1042
Handlebars — {{...}} sintaksisli template dvigatel.
O'rnatish
npm install nodemailer handlebars
Template fayl
emails/
├── xush-kelibsiz.hbs
├── parol-tiklash.hbs
└── buyurtma-tasdiqlash.hbs
<!-- emails/xush-kelibsiz.hbs -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; }
.container { max-width: 600px; margin: 40px auto; background: white; border-radius: 8px; padding: 40px; }
.btn { display: inline-block; background: #3b82f6; color: white; padding: 12px 28px; border-radius: 6px; text-decoration: none; }
</style>
</head>
<body>
<div class="container">
<h2>Salom, {{ism}}! 👋</h2>
<p>DevDocs ga xush kelibsiz. Hisob ma'lumotlaringiz:</p>
<ul>
<li>Email: {{email}}</li>
<li>Sana: {{sana}}</li>
</ul>
<a href="{{loginUrl}}" class="btn">Kirish</a>
<p style="color:#888;margin-top:32px;font-size:13px;">
Bu emailni kutmagan bo'lsangiz — e'tibor bermang.
</p>
</div>
</body>
</html>
Template ni render qilish
import fs from 'fs';
import path from 'path';
import handlebars from 'handlebars';
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
// Template faylni o'qib, render qilish
const emailHtml = (templateNomi, malumotlar) => {
const fayl = path.join(process.cwd(), 'emails', `${templateNomi}.hbs`);
const manba = fs.readFileSync(fayl, 'utf8'); // ← faylni o'qish
const template = handlebars.compile(manba); // ← template yaratish
return template(malumotlar); // ← render qilish
};
// Email yuborish
const emailYuborish = async ({ kimga, mavzu, template, malumotlar }) => {
const html = emailHtml(template, malumotlar);
await transporter.sendMail({
from: `"DevDocs" <${process.env.EMAIL_USER}>`,
to: kimga,
subject: mavzu,
html,
});
};
Ishlatish:
await emailYuborish({
kimga: 'ali@example.com',
mavzu: 'DevDocs ga xush kelibsiz!',
template: 'xush-kelibsiz',
malumotlar: {
ism: 'Ali',
email: 'ali@example.com',
sana: '3 iyun 2026',
loginUrl: 'https://devdocs.uz/login',
},
});
Handlebars — asosiy sintaksis
O'zgaruvchi:
<p>{{ism}}</p>
Shart:
{{#if tasdiqlangan}}
<p>Hisobingiz tasdiqlangan ✓</p>
{{else}}
<p>Iltimos, emailni tasdiqlang.</p>
{{/if}}
Ro'yxat:
{{#each mahsulotlar}}
<li>{{this.nomi}} — {{this.narxi}} so'm</li>
{{/each}}
Buyurtma tasdiqlash email misoli:
<!-- emails/buyurtma.hbs -->
<div class="container">
<h2>Buyurtma #{{raqam}} qabul qilindi!</h2>
<table>
<tr><th>Mahsulot</th><th>Soni</th><th>Narxi</th></tr>
{{#each mahsulotlar}}
<tr>
<td>{{this.nomi}}</td>
<td>{{this.soni}}</td>
<td>{{this.narxi}} so'm</td>
</tr>
{{/each}}
</table>
<p><strong>Jami: {{jami}} so'm</strong></p>
</div>
await emailYuborish({
kimga: 'ali@example.com',
mavzu: 'Buyurtmangiz qabul qilindi',
template: 'buyurtma',
malumotlar: {
raqam: 1042,
mahsulotlar: [
{ nomi: 'Node.js kursi', soni: 1, narxi: '150 000' },
{ nomi: 'React kursi', soni: 1, narxi: '120 000' },
],
jami: '270 000',
},
});
Template ni keshlash
Har safar fs.readFileSync — diskdan o'qish. Ko'p foydalanuvchi bo'lsa sekinlashadi. Bir marta o'qib xotirada saqlang:
const kesh = {}; // ← xotirada saqlanadi
const emailHtml = (templateNomi, malumotlar) => {
if (!kesh[templateNomi]) {
const fayl = path.join(process.cwd(), 'emails', `${templateNomi}.hbs`);
const manba = fs.readFileSync(fayl, 'utf8');
kesh[templateNomi] = handlebars.compile(manba); // ← bir marta compile
}
return kesh[templateNomi](malumotlar); // ← keshdan ishlatish
};
💡 Production da server bir marta ishga tushganda barcha templatelar bitta keshlanadi. Development da esa o'zgarishlarni ko'rish uchun server qayta ishga tushiriladi — muammo emas.
Tez-tez uchraydigan xatolar
1. Template topilmadi
Error: ENOENT: no such file or directory
// ❌ — noto'g'ri yo'l
path.join(__dirname, 'emails', `${templateNomi}.hbs`)
// ES module da __dirname yo'q!
// ✅ — ES module da to'g'ri yo'l
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
path.join(__dirname, 'emails', `${templateNomi}.hbs`);
// Yoki
path.join(process.cwd(), 'emails', `${templateNomi}.hbs`);
2. O'zgaruvchi ko'rsatilmayapti
// ❌ — katta-kichik harf farqi
template: `{{Ism}}`
malumotlar: { ism: 'Ali' } // ← kichik harf, mos kelmaydi
// ✅
template: `{{ism}}`
malumotlar: { ism: 'Ali' }
3. HTML kod sifatida chiqib ketishi (XSS)
<!-- Handlebars {{ }} HTML ni escape qiladi — xavfsiz -->
{{ism}} → Ali (xavfsiz)
<!-- {{{ }}} escape qilmaydi — faqat o'zingiz boshqarayotgan ma'lumot uchun -->
{{{html}}} → <b>Ali</b> (foydalanuvchi ma'lumotida ishlatmang!)
Xulosa
- Template —
{{o'zgaruvchi}}joylashtirilgan HTML fayl; Handlebars render qiladi handlebars.compile(manba)(malumotlar)— bir qadamda HTML{{#if}},{{#each}}— shartlar va ro'yxatlar- Template ni keshlang — har safar diskdan o'qimang
{{ }}— XSS dan himoya qiladi;{{{ }}}— qilmaydi, ehtiyot bo'ling
Keyingi dars: 48-dars: SMS — Twilio bilan