From 8f3d1ae5efe34029d1fa602d55ebbc4ba682c2d1 Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 18 May 2026 11:28:46 +0500 Subject: [PATCH] feat: add email notification channel --- server/src/lib/email.js | 39 +++++++++++++++---- .../notifications/channels/email-channel.js | 37 ++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 server/src/lib/notifications/channels/email-channel.js diff --git a/server/src/lib/email.js b/server/src/lib/email.js index 97b4c9d..242091e 100644 --- a/server/src/lib/email.js +++ b/server/src/lib/email.js @@ -4,14 +4,8 @@ function hasSmtpEnv() { return Boolean(process.env.SMTP_HOST && process.env.SMTP_PORT && process.env.SMTP_USER && process.env.SMTP_PASS) } -export async function sendLoginCodeEmail({ to, code }) { - if (!hasSmtpEnv()) { - // dev fallback - console.log(`[DEV] login code for ${to}: ${code}`) - return - } - - const transporter = nodemailer.createTransport({ +function createTransporter() { + return nodemailer.createTransport({ host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT), secure: process.env.SMTP_SECURE === 'true', @@ -20,7 +14,15 @@ export async function sendLoginCodeEmail({ to, code }) { pass: process.env.SMTP_PASS, }, }) +} +export async function sendLoginCodeEmail({ to, code }) { + if (!hasSmtpEnv()) { + console.log(`[DEV] login code for ${to}: ${code}`) + return + } + + const transporter = createTransporter() const from = process.env.MAIL_FROM || process.env.SMTP_USER await transporter.sendMail({ @@ -31,3 +33,24 @@ export async function sendLoginCodeEmail({ to, code }) { }) } +export async function sendNotificationEmail({ to, subject, html }) { + if (!hasSmtpEnv()) { + console.log(`[DEV] notification email to ${to}: ${subject}`) + return { success: true } + } + + try { + const transporter = createTransporter() + const from = process.env.MAIL_FROM || process.env.SMTP_USER + + await transporter.sendMail({ + from, + to, + subject, + html, + }) + return { success: true } + } catch (err) { + return { success: false, error: err.message } + } +} diff --git a/server/src/lib/notifications/channels/email-channel.js b/server/src/lib/notifications/channels/email-channel.js new file mode 100644 index 0000000..8911ea3 --- /dev/null +++ b/server/src/lib/notifications/channels/email-channel.js @@ -0,0 +1,37 @@ +// server/src/lib/notifications/channels/email-channel.js +import { sendNotificationEmail } from '../../email.js' +import { + renderOrderCreatedEmail, + renderOrderStatusChangedEmail, + renderOrderMessageEmail, + renderPaymentStatusChangedEmail, + renderAdminOrderCreatedEmail, + renderAdminNewReviewEmail, + renderAuthCodeEmail, +} from '../templates/email-templates.js' + +const templateRenderers = { + 'order:created': renderOrderCreatedEmail, + 'order:statusChanged': renderOrderStatusChangedEmail, + 'orderMessage:adminReply': renderOrderMessageEmail, + 'payment:statusChanged': renderPaymentStatusChangedEmail, + 'order:created:admin': renderAdminOrderCreatedEmail, + 'orderMessage:sent': renderOrderMessageEmail, + 'review:created': renderAdminNewReviewEmail, + 'auth:codeRequested': renderAuthCodeEmail, +} + +export const emailChannel = { + name: 'email', + + async send({ recipient, eventType, payload }) { + const renderer = templateRenderers[eventType] + if (!renderer) { + return { success: false, error: `No email template for event: ${eventType}` } + } + + const { subject, html } = renderer(payload) + const result = await sendNotificationEmail({ to: recipient, subject, html }) + return result + }, +}