From 7ee349aa0f7479fe1ede0d79bfb0248b562d8134 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Tue, 4 Nov 2025 22:54:33 +0000 Subject: [PATCH] Replace Resend with Hostinger SMTP email service cgen-3c9a10ea2b574d2b92b4ec3981e0ded7 --- server/email.ts | 60 ++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/server/email.ts b/server/email.ts index f1ad8e5c..a64a1000 100644 --- a/server/email.ts +++ b/server/email.ts @@ -1,16 +1,40 @@ -import { Resend } from "resend"; +import nodemailer from "nodemailer"; -const resendApiKey = process.env.RESEND_API_KEY; -const defaultFromAddress = - process.env.RESEND_FROM_EMAIL ?? "AeThex OS "; +const smtpHost = process.env.SMTP_HOST; +const smtpPort = parseInt(process.env.SMTP_PORT || "465", 10); +const smtpUser = process.env.SMTP_USER; +const smtpPassword = process.env.SMTP_PASSWORD; +const fromEmail = process.env.SMTP_FROM_EMAIL || "no-reply@aethex.dev"; const verifySupportEmail = process.env.VERIFY_SUPPORT_EMAIL ?? "support@aethex.biz"; -const resendClient = resendApiKey ? new Resend(resendApiKey) : null; +let transporter: nodemailer.Transporter | null = null; + +function getTransporter() { + if (transporter) return transporter; + + if (!smtpHost || !smtpUser || !smtpPassword) { + throw new Error( + "SMTP configuration is missing. Set SMTP_HOST, SMTP_USER, and SMTP_PASSWORD.", + ); + } + + transporter = nodemailer.createTransport({ + host: smtpHost, + port: smtpPort, + secure: smtpPort === 465, + auth: { + user: smtpUser, + pass: smtpPassword, + }, + }); + + return transporter; +} export const emailService = { get isConfigured() { - return Boolean(resendClient); + return Boolean(smtpHost && smtpUser && smtpPassword); }, async sendVerificationEmail(params: { @@ -18,10 +42,7 @@ export const emailService = { verificationUrl: string; fullName?: string | null; }) { - if (!resendClient) { - throw new Error("Email service is not configured. Set RESEND_API_KEY."); - } - + const transporter = getTransporter(); const { to, verificationUrl, fullName } = params; const safeName = fullName?.trim() || "there"; @@ -39,7 +60,7 @@ export const emailService = {

${verificationUrl}


- Didnt create an account? Please ignore this email or contact ${verifySupportEmail}. + Didn't create an account? Please ignore this email or contact ${verifySupportEmail}.

`; @@ -53,8 +74,8 @@ export const emailService = { `If you didn't request this, contact us at ${verifySupportEmail}.`, ].join("\n"); - await resendClient.emails.send({ - from: defaultFromAddress, + await transporter.sendMail({ + from: fromEmail, to, subject, html, @@ -63,8 +84,6 @@ export const emailService = { "X-AeThex-Email": "verification", "X-Entity-Ref-ID": verificationUrl.slice(-24), }, - tags: [{ name: "template", value: "auth-verification" }], - reply_to: verifySupportEmail, }); }, @@ -74,10 +93,7 @@ export const emailService = { inviterName?: string | null; message?: string | null; }) { - if (!resendClient) { - throw new Error("Email service is not configured. Set RESEND_API_KEY."); - } - + const transporter = getTransporter(); const { to, inviteUrl, inviterName, message } = params; const safeInviter = inviterName?.trim() || "An AeThex member"; @@ -103,15 +119,13 @@ export const emailService = { inviteUrl, ].join("\n"); - await resendClient.emails.send({ - from: defaultFromAddress, + await transporter.sendMail({ + from: fromEmail, to, subject, html, text, headers: { "X-AeThex-Email": "invite" }, - tags: [{ name: "template", value: "invite" }], - reply_to: verifySupportEmail, }); }, };