From 9a8b70a9c33a12734e013d72849e5455d1a6e4e7 Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 5 May 2026 22:52:25 +0000 Subject: [PATCH] fix: resolve '>' in From field and logo not rendering in auto-reply - Add getFromEmail() to extract bare email from SMTP_FROM (fixes '>' leak) - Replace data:image base64 URI with CID attachment (works in all email clients) - Add src/assets/logo.png as embedded CID attachment in auto-reply - Fallback to text logo if logo file is missing --- src/assets/logo.png | Bin 0 -> 8396 bytes src/services/email.js | 59 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 src/assets/logo.png diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e3bc710b9a474d76805323f5d8ff3d392c775aee GIT binary patch literal 8396 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 ZF9=RuM#-l0eFZlF>1&&4)oMD&{x1;WLqPxl literal 0 HcmV?d00001 diff --git a/src/services/email.js b/src/services/email.js index 02b8887..6e58fca 100644 --- a/src/services/email.js +++ b/src/services/email.js @@ -1,4 +1,7 @@ import nodemailer from 'nodemailer'; +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; /** * Create a Nodemailer transport using SMTP environment variables. @@ -36,11 +39,39 @@ const createTransport = () => { }); }; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +/** + * Read the logo PNG file for embedding in emails. + * Returns a Buffer, or null if the file doesn't exist. + */ +let _logoBuffer = null; +const getLogoBuffer = () => { + if (_logoBuffer) return _logoBuffer; + try { + _logoBuffer = readFileSync(join(__dirname, '..', 'assets', 'logo.png')); + } catch { + // silently ignore if logo file is not found + } + return _logoBuffer; +}; + const getFromAddress = () => process.env.SMTP_FROM || process.env.SMTP_USER; +/** + * Return just the bare email address (used for envelope sender). + * Strips any display-name or angle brackets from SMTP_FROM. + */ +const getFromEmail = () => { + const raw = process.env.SMTP_FROM || process.env.SMTP_USER; + const match = raw.match(/<([^>]+)>/); + return match ? match[1] : raw; +}; + const getContactEmail = () => - process.env.CONTACT_EMAIL || process.env.SMTP_USER; + process.env.CONTACT_EMAIL || getFromEmail(); /** * Verify that the SMTP transport can connect. @@ -78,7 +109,7 @@ export const sendContactEmail = async ({ name, email, company, message }) => { const companyLine = company ? `Company: ${company}\n` : ''; const info = await transporter.sendMail({ - from: `"Moxie Contact Form" <${from}>`, + from: `"Moxie Contact Form" <${getFromEmail()}>`, to, replyTo: email, // allow replying directly to the submitter subject: `[Moxie] New Contact Form Submission from ${name}`, @@ -143,12 +174,27 @@ export const sendContactEmail = async ({ name, email, company, message }) => { export const sendAutoReply = async ({ name, email }) => { try { const transporter = createTransport(); - const from = getFromAddress(); + const logoBuf = getLogoBuffer(); + + // Build attachments array — logo as CID if available + const attachments = []; + const logoHtmlTag = logoBuf + ? 'Moxiegen' + : 'Moxiegen'; + + if (logoBuf) { + attachments.push({ + filename: 'logo.png', + content: logoBuf, + cid: 'moxiegen-logo', // same CID as referenced in the HTML img tag + }); + } const info = await transporter.sendMail({ - from: `"Moxiegen" <${from}>`, + from: `"Moxiegen" <${getFromEmail()}>`, to: email, subject: 'Thank you for contacting Moxiegen', + attachments, text: [ `Dear ${name},`, '', @@ -171,7 +217,7 @@ export const sendAutoReply = async ({ name, email }) => { html: `
- Moxiegen + ${logoHtmlTag}

Algorithmic Enhancement for Enterprise AI

@@ -185,8 +231,7 @@ export const sendAutoReply = async ({ name, email }) => {

Need a faster response?

- Call us at +1 (855) 246-6943 or email - info@moxiegen.com + Call us at +1 (855) 246-6943 or email info@moxiegen.com