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