email.ts raw

   1  function dbConfig(key: string): string {
   2    try {
   3      const { getDb } = require('./db');
   4      const row = getDb().query('SELECT value FROM content WHERE key = ?').get(key) as { value: string } | null;
   5      return row?.value || '';
   6    } catch { return ''; }
   7  }
   8  
   9  function cfg(): { host: string; port: number; user: string; pass: string; from: string } {
  10    return {
  11      host: dbConfig('smtp_host') || process.env.SMTP_HOST || '',
  12      port: parseInt(dbConfig('smtp_port') || process.env.SMTP_PORT || '587'),
  13      user: dbConfig('smtp_user') || process.env.SMTP_USER || '',
  14      pass: dbConfig('smtp_pass') || process.env.SMTP_PASS || '',
  15      from: dbConfig('smtp_from') || process.env.MAIL_FROM || 'shop@zlattea.com',
  16    };
  17  }
  18  
  19  function enabled(): boolean {
  20    const c = cfg();
  21    return !!(c.host && c.port && c.user && c.pass);
  22  }
  23  
  24  // minimal text/plain email via direct SMTP/TLS. for production, consider an
  25  // external transactional mail api. this path is acceptable for local dev until
  26  // volume requires more.
  27  export async function sendMail(to: string, subject: string, body: string) {
  28    if (!enabled()) { console.log('email skipped (no SMTP config):', subject, to); return; }
  29    const c = cfg();
  30    const conn = await require('node:tls').connect({ host: c.host, port: c.port, servername: c.host });
  31    const send = (s: string) => conn.write(s + '\r\n');
  32    let buf = '';
  33    const readUntil = (code: number, timeoutMs = 10000): Promise<string> => new Promise((resolve, reject) => {
  34      const t = setTimeout(() => reject(new Error('smtp timeout')), timeoutMs);
  35      const onData = (d: Buffer) => {
  36        buf += d.toString();
  37        if (buf.includes('\r\n')) {
  38          const lines = buf.split('\r\n');
  39          const last = lines[lines.length - 2] || '';
  40          if (last.startsWith(String(code)) || last.startsWith(String(code) + ' ')) {
  41            clearTimeout(t);
  42            conn.off('data', onData);
  43            resolve(last);
  44          }
  45        }
  46      };
  47      conn.on('data', onData);
  48    });
  49    await readUntil(220);
  50    send(`EHLO ${c.host}`);
  51    await readUntil(250);
  52    send('AUTH LOGIN');
  53    await readUntil(334);
  54    send(Buffer.from(c.user).toString('base64'));
  55    await readUntil(334);
  56    send(Buffer.from(c.pass).toString('base64'));
  57    await readUntil(235);
  58    send(`MAIL FROM:<${c.from}>`);
  59    await readUntil(250);
  60    send(`RCPT TO:<${to}>`);
  61    await readUntil(250);
  62    send('DATA');
  63    await readUntil(354);
  64    const msg = [
  65      `From: Zlattea <${c.from}>`,
  66      `To: ${to}`,
  67      `Subject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=`,
  68      'Content-Type: text/plain; charset=utf-8',
  69      '',
  70      body,
  71      '.',
  72    ].join('\r\n');
  73    send(msg);
  74    await readUntil(250);
  75    send('QUIT');
  76    conn.end();
  77  }
  78  
  79  export function orderCreatedEmail(to: string, orderId: string, viewToken: string, totalLv: string, itemsDesc: string) {
  80    if (!enabled()) return;
  81    const url = `${process.env.SITE_URL || 'http://localhost:3000'}/order/${orderId}?t=${viewToken}`;
  82    sendMail(to, `Поръчка ${orderId.slice(0, 8)} - Златен чай`,
  83      `Благодарим за поръчката! Вашият номер на поръчка: ${orderId.slice(0, 8)}\n\n` +
  84      `Продукти:\n${itemsDesc}\n\nОбщо: ${totalLv}\n\n` +
  85      `Следете статуса тук: ${url}\n\n` +
  86      `Ако сте избрали банков превод или плащане с Lightning/Bitcoin, ще получите допълнителни инструкции на тази страница.\n\n` +
  87      `Златен чай`);
  88  }
  89  
  90  export function paymentConfirmedEmail(to: string, orderId: string) {
  91    if (!enabled()) return;
  92    sendMail(to, `Плащането е получено - Поръчка ${orderId.slice(0, 8)}`,
  93      `Плащането за поръчка ${orderId.slice(0, 8)} е получено. Ще ви известим при изпращане.\n\nЗлатен чай`);
  94  }
  95