API based email sender

This commit is contained in:
2026-07-14 12:14:23 +02:00
parent 7644e4768c
commit bcb84f4de4
11 changed files with 295 additions and 193 deletions
+73 -120
View File
@@ -1,7 +1,6 @@
import express from 'express';
import path from 'path';
import fs from 'fs';
import nodemailer from 'nodemailer';
import { createServer as createViteServer } from 'vite';
import dotenv from 'dotenv';
@@ -13,156 +12,110 @@ async function startServer() {
app.use(express.json());
// API route to send email via SMTP
app.post('/api/send-email', async (req, res) => {
// API route to send email via SMTP (supporting both standard and PHP endpoint pathways)
app.post(['/api/send-email', '/send-email.php'], async (req, res) => {
const { name, email, phone, company, subject, message, isEstimate, estimateSummary } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Missing name or email' });
}
// Load SMTP configuration (favoring env variables, falling back to JSON file)
const smtpConfig = {
host: process.env.SMTP_HOST || 'smtp.khongisa.co.za',
port: Number(process.env.SMTP_PORT || 587),
secure: process.env.SMTP_SECURE === 'true',
user: process.env.SMTP_USER || '',
pass: process.env.SMTP_PASS || '',
fromEmail: process.env.SMTP_FROM || '',
toEmail: process.env.SMTP_TO || 'info@litecharms.co.za',
};
// If environment variables aren't completely defined, try loading from smtp-config.json
const configPath = path.join(process.cwd(), 'smtp-config.json');
if ((!smtpConfig.user || !smtpConfig.pass || smtpConfig.pass.includes('YOUR_SECURE')) && fs.existsSync(configPath)) {
try {
const fileData = fs.readFileSync(configPath, 'utf8');
const parsed = JSON.parse(fileData);
smtpConfig.host = parsed.host || smtpConfig.host;
smtpConfig.port = Number(parsed.port || smtpConfig.port);
smtpConfig.secure = parsed.secure ?? smtpConfig.secure;
smtpConfig.user = parsed.auth?.user || smtpConfig.user;
smtpConfig.pass = parsed.auth?.pass || smtpConfig.pass;
smtpConfig.fromEmail = parsed.fromEmail || smtpConfig.fromEmail;
smtpConfig.toEmail = parsed.toEmail || smtpConfig.toEmail;
} catch (err) {
console.error('Error reading smtp-config.json:', err);
}
}
// Generate Email Content
let emailSubject = subject || 'LiteCharms Inquiry';
if (isEstimate) {
emailSubject = `LiteCharms Project Estimate Proposal: ${subject}`;
const emailSubject = isEstimate
? `LiteCharms Project Estimate Proposal: ${subject}`
: `LiteCharms General Service Inquiry: ${subject}`;
let breakdownHtml = '';
if (isEstimate && estimateSummary && Array.isArray(estimateSummary.breakdown)) {
breakdownHtml = estimateSummary.breakdown
.map((b: any) => `<li>${b.item}: ${b.price}</li>`)
.join('');
}
let emailHtml = `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; color: #1e293b;">
<div style="background-color: #020617; padding: 24px; text-align: center; border-bottom: 2px solid #00bcac;">
<h2 style="color: #ffffff; margin: 0; font-size: 22px; letter-spacing: -0.02em;">Lite<span style="color: #00bcac;">Charms</span></h2>
<p style="color: #94a3b8; margin: 4px 0 0 0; font-size: 12px; font-family: monospace;">MIDRAND, SOUTH AFRICA</p>
</div>
<div style="padding: 24px; background-color: #ffffff;">
<h3 style="color: #00bcac; margin-top: 0; margin-bottom: 16px; font-size: 18px;">${isEstimate ? 'New Project Estimate Inquiry' : 'New General Service Inquiry'}</h3>
<table style="width: 100%; border-collapse: collapse; margin-bottom: 24px;">
const emailHtml = `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; color: #1e293b;">
<div style="background-color: #020617; padding: 24px; text-align: center; border-bottom: 2px solid #00bcac;">
<h2 style="color: #ffffff; margin: 0; font-size: 22px; letter-spacing: -0.02em;">Lite<span style="color: #00bcac;">Charms</span></h2>
<p style="color: #94a3b8; margin: 4px 0 0 0; font-size: 12px; font-family: monospace;">MIDRAND, SOUTH AFRICA</p>
</div>
<div style="padding: 24px; background-color: #ffffff;">
<h3 style="color: #00bcac; margin-top: 0; margin-bottom: 16px; font-size: 18px;">${isEstimate ? 'New Project Estimate Inquiry' : 'New General Service Inquiry'}</h3>
<table style="width: 100%; border-collapse: collapse; margin-bottom: 24px;">
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 8px 0; font-weight: bold; width: 120px; font-size: 13px; color: #64748b;">Client Name:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${name}</td>
<td style="padding: 8px 0; font-weight: bold; width: 120px; font-size: 13px; color: #64748b;">Client Name:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${name}</td>
</tr>
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Email Address:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;"><a href="mailto:${email}">${email}</a></td>
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Email Address:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;"><a href="mailto:${email}">${email}</a></td>
</tr>
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Phone Number:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${phone || 'Not Provided'}</td>
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Phone Number:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${phone || 'Not Provided'}</td>
</tr>
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Company:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${company || 'Not Provided'}</td>
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Company:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${company || 'Not Provided'}</td>
</tr>
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Subject:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${subject || 'General Service Contact'}</td>
<td style="padding: 8px 0; font-weight: bold; font-size: 13px; color: #64748b;">Subject:</td>
<td style="padding: 8px 0; font-size: 13px; color: #1e293b;">${subject}</td>
</tr>
</table>
`;
</table>
if (isEstimate && estimateSummary) {
emailHtml += `
${isEstimate && estimateSummary ? `
<div style="background-color: #f8fafc; border: 1px dashed #cbd5e1; padding: 16px; border-radius: 6px; margin-bottom: 24px;">
<h4 style="margin-top: 0; color: #020617; font-size: 14px; border-bottom: 1px solid #e2e8f0; padding-bottom: 6px;">Selected Scope & Custom Pricing Range</h4>
<div style="margin-top: 8px; font-size: 13px;">
<strong>Estimated Range:</strong> <span style="color: #00bcac; font-weight: bold;">R ${estimateSummary.totalMin?.toLocaleString('en-ZA')}</span> to <span style="color: #e11d48; font-weight: bold;">R ${estimateSummary.totalMax?.toLocaleString('en-ZA')}</span>
<br/><strong>Roadmap:</strong> ~${estimateSummary.timelineWeeks} Weeks
</div>
<div style="margin-top: 12px; font-size: 11px; color: #64748b;">
<strong>Breakdown items:</strong>
<ul style="margin: 4px 0 0 0; padding-left: 16px;">
${(estimateSummary.breakdown || []).map((b: any) => `<li>${b.item}: ${b.price}</li>`).join('')}
</ul>
</div>
<h4 style="margin-top: 0; color: #020617; font-size: 14px; border-bottom: 1px solid #e2e8f0; padding-bottom: 6px;">Selected Scope & Custom Pricing Range</h4>
<div style="margin-top: 8px; font-size: 13px;">
<strong>Estimated Range:</strong> <span style="color: #00bcac; font-weight: bold;">R ${estimateSummary.totalMin?.toLocaleString('en-ZA')}</span> to <span style="color: #e11d48; font-weight: bold;">R ${estimateSummary.totalMax?.toLocaleString('en-ZA')}</span>
<br/><strong>Roadmap:</strong> ~${estimateSummary.timelineWeeks} Weeks
</div>
${breakdownHtml ? `
<div style="margin-top: 12px; font-size: 11px; color: #64748b;">
<strong>Breakdown items:</strong>
<ul style="margin: 4px 0 0 0; padding-left: 16px;">
${breakdownHtml}
</ul>
</div>
` : ''}
</div>
`;
}
` : ''}
emailHtml += `
<div style="margin-bottom: 24px;">
<div style="margin-bottom: 24px;">
<h4 style="margin-top: 0; margin-bottom: 8px; font-size: 14px; color: #020617;">Client Message / Brief:</h4>
<p style="background-color: #f8fafc; padding: 12px; border-radius: 6px; font-size: 13px; line-height: 1.5; color: #334155; white-space: pre-wrap; margin: 0;">${message || 'No additional notes provided.'}</p>
</div>
<p style="background-color: #f8fafc; padding: 12px; border-radius: 6px; font-size: 13px; line-height: 1.5; color: #334155; white-space: pre-wrap; margin: 0;">${(message || 'No additional notes provided.').replace(/\n/g, '<br/>')}</p>
</div>
<div style="background-color: #f1f5f9; padding: 16px; text-align: center; font-size: 11px; color: #64748b; border-top: 1px solid #e2e8f0;">
This is an automated dispatch from khongisa.co.za Private Cloud platform for LiteCharms (PTY) Ltd.
</div>
</div>
`;
// Check if SMTP is ready or left as placeholder
const hasValidAuth = smtpConfig.user && smtpConfig.pass && !smtpConfig.pass.includes('YOUR_SECURE');
if (!hasValidAuth) {
console.log('\n--- SMTP EMAIL LOG (NO PASSWORD CONFIGURED) ---');
console.log(`To: ${smtpConfig.toEmail}`);
console.log(`From: ${smtpConfig.fromEmail || 'no-reply@khongisa.co.za'}`);
console.log(`Subject: ${emailSubject}`);
console.log(`Plain text summary:\nClient ${name} (${email}) sent inquiry. Subject: ${emailSubject}`);
console.log('-----------------------------------------------\n');
return res.status(200).json({
success: true,
message: 'SMTP credentials not configured yet. Email dispatch was logged in system console successfully.',
logged: true
});
}
</div>
<div style="background-color: #f1f5f9; padding: 16px; text-align: center; font-size: 11px; color: #64748b; border-top: 1px solid #e2e8f0;">
This is an automated dispatch from the LiteCharms website server.
</div>
</div>
`;
try {
const transporter = nodemailer.createTransport({
host: smtpConfig.host,
port: smtpConfig.port,
secure: smtpConfig.secure,
auth: {
user: smtpConfig.user,
pass: smtpConfig.pass,
const response = await fetch('https://messenger.api.khongisa.co.za/api/send-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
htmlBody: emailHtml,
from: 'contact@litecharms.co.za',
to: 'contact@litecharms.co.za',
subject: emailSubject,
}),
});
await transporter.sendMail({
from: smtpConfig.fromEmail || smtpConfig.user,
to: smtpConfig.toEmail,
subject: emailSubject,
html: emailHtml,
});
return res.json({ success: true, message: 'Email sent successfully via SMTP' });
if (response.ok) {
return res.json({ success: true, message: 'Email sent successfully via Messenger API' });
} else {
const errorText = await response.text();
return res.status(500).json({ error: 'External API Error', details: errorText });
}
} catch (error: any) {
console.error('SMTP Email Send Error:', error);
return res.status(500).json({
error: 'SMTP Connection failed',
details: error.message,
message: 'Failed to send email via SMTP post-deployment. Please verify your host, user, and password inside smtp-config.json.'
});
console.error('Server proxy email send error:', error);
return res.status(500).json({ error: 'Server error sending email', details: error.message });
}
});