From 5bc6772f648cb0d6ed2509d3006920b3530cf81c Mon Sep 17 00:00:00 2001 From: root Date: Sun, 17 May 2026 16:36:34 -0400 Subject: [PATCH] fix the email sent --- .env.example | 2 +- apps/api/src/services/notificationService.ts | 162 +++++++++++-------- 2 files changed, 96 insertions(+), 68 deletions(-) diff --git a/.env.example b/.env.example index b96d1c1..16c4097 100644 --- a/.env.example +++ b/.env.example @@ -48,7 +48,7 @@ NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name # ─── Resend (Email) ──────────────────────────────────────────── RESEND_API_KEY=re_... -EMAIL_FROM=noreply@rentaldrivego.com +EMAIL_FROM=rentaldrivego@gmail.com EMAIL_FROM_NAME=RentalDriveGo # ─── Twilio (SMS + WhatsApp) ─────────────────────────────────── diff --git a/apps/api/src/services/notificationService.ts b/apps/api/src/services/notificationService.ts index 6eb3987..bb1d54b 100644 --- a/apps/api/src/services/notificationService.ts +++ b/apps/api/src/services/notificationService.ts @@ -5,11 +5,15 @@ import { prisma } from '../lib/prisma' import { redis } from '../lib/redis' import { NotificationType, NotificationChannel } from '@rentaldrivego/database' -const resend = - process.env.RESEND_API_KEY && process.env.RESEND_API_KEY !== 're_...' - ? new Resend(process.env.RESEND_API_KEY) +const resendApiKey = + process.env.RESEND_API_KEY && + process.env.RESEND_API_KEY !== 're_...' && + process.env.RESEND_API_KEY.startsWith('re_') + ? process.env.RESEND_API_KEY : null +const resend = resendApiKey ? new Resend(resendApiKey) : null + const emailFromAddress = process.env.EMAIL_FROM && process.env.EMAIL_FROM !== 'noreply@example.com' ? process.env.EMAIL_FROM @@ -125,6 +129,85 @@ function renderEmailHtml(body: string) { .join('') } +function resolveSmtpReplyTo() { + if (!process.env.MAIL_REPLY_TO_ADDRESS || process.env.MAIL_REPLY_TO_ADDRESS.includes('${')) { + return undefined + } + + if (process.env.MAIL_REPLY_TO_NAME && !process.env.MAIL_REPLY_TO_NAME.includes('${')) { + return `${process.env.MAIL_REPLY_TO_NAME} <${process.env.MAIL_REPLY_TO_ADDRESS}>` + } + + return process.env.MAIL_REPLY_TO_ADDRESS +} + +async function sendEmailWithProviders(opts: { + to: string + subject: string + html: string + text: string +}) { + const errors: string[] = [] + + if (resend) { + try { + if (!emailFromAddress || !emailFromName) { + throw new Error('Email sender identity is not configured') + } + + const { data, error } = await resend.emails.send({ + from: `${emailFromName} <${emailFromAddress}>`, + to: opts.to, + subject: opts.subject, + html: opts.html, + text: opts.text, + }) + + if (error) { + throw new Error(error.message) + } + + return { + provider: 'resend' as const, + providerMessageId: data?.id ?? null, + } + } catch (err: any) { + errors.push(`Resend: ${err?.message ?? String(err)}`) + console.warn('[Notifications] Resend delivery failed, falling back if possible:', err?.message ?? String(err)) + } + } + + if (smtpTransport) { + try { + if (!smtpFromAddress) { + throw new Error('SMTP sender identity is not configured') + } + + const info = await smtpTransport.sendMail({ + from: smtpFromName ? `${smtpFromName} <${smtpFromAddress}>` : smtpFromAddress, + to: opts.to, + subject: opts.subject, + html: opts.html, + text: opts.text, + replyTo: resolveSmtpReplyTo(), + }) + + return { + provider: 'smtp' as const, + providerMessageId: info.messageId ?? null, + } + } catch (err: any) { + errors.push(`SMTP: ${err?.message ?? String(err)}`) + } + } + + if (errors.length) { + throw new Error(errors.join(' | ')) + } + + throw new Error('No email provider is configured') +} + export async function sendNotification(opts: SendNotificationOptions) { const results: Array<{ channel: NotificationChannel; success: boolean; error?: string }> = [] @@ -148,44 +231,14 @@ export async function sendNotification(opts: SendNotificationOptions) { let success = false if (channel === 'EMAIL' && opts.email) { - if (resend) { - if (!emailFromAddress || !emailFromName) { - throw new Error('Email sender identity is not configured') - } - const { data, error } = await resend.emails.send({ - from: `${emailFromName} <${emailFromAddress}>`, - to: opts.email, - subject: opts.title, - html: renderEmailHtml(opts.body), - text: opts.body, - }) - if (error) { - throw new Error(error.message) - } - providerMessageId = data?.id ?? null - success = true - } else if (smtpTransport) { - if (!smtpFromAddress) { - throw new Error('SMTP sender identity is not configured') - } - const info = await smtpTransport.sendMail({ - from: smtpFromName ? `${smtpFromName} <${smtpFromAddress}>` : smtpFromAddress, - to: opts.email, - subject: opts.title, - html: renderEmailHtml(opts.body), - text: opts.body, - replyTo: - process.env.MAIL_REPLY_TO_ADDRESS && !process.env.MAIL_REPLY_TO_ADDRESS.includes('${') - ? process.env.MAIL_REPLY_TO_NAME && !process.env.MAIL_REPLY_TO_NAME.includes('${') - ? `${process.env.MAIL_REPLY_TO_NAME} <${process.env.MAIL_REPLY_TO_ADDRESS}>` - : process.env.MAIL_REPLY_TO_ADDRESS - : undefined, - }) - providerMessageId = info.messageId ?? null - success = true - } else { - throw new Error('No email provider is configured') - } + const emailResult = await sendEmailWithProviders({ + to: opts.email, + subject: opts.title, + html: renderEmailHtml(opts.body), + text: opts.body, + }) + providerMessageId = emailResult.providerMessageId + success = true } if (channel === 'SMS' && opts.phone) { @@ -259,30 +312,5 @@ export async function sendTransactionalEmail(opts: { html: string text: string }) { - if (resend) { - if (!emailFromAddress || !emailFromName) throw new Error('Email sender identity is not configured') - const { error } = await resend.emails.send({ - from: `${emailFromName} <${emailFromAddress}>`, - to: opts.to, - subject: opts.subject, - html: opts.html, - text: opts.text, - }) - if (error) throw new Error(error.message) - return - } - - if (smtpTransport) { - if (!smtpFromAddress) throw new Error('SMTP sender identity is not configured') - await smtpTransport.sendMail({ - from: smtpFromName ? `${smtpFromName} <${smtpFromAddress}>` : smtpFromAddress, - to: opts.to, - subject: opts.subject, - html: opts.html, - text: opts.text, - }) - return - } - - throw new Error('No email provider is configured') + await sendEmailWithProviders(opts) }