Email template — html email, handlebars

~15 daq · Express · Email va Xabarlar

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