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
+9
View File
@@ -0,0 +1,9 @@
# GEMINI_API_KEY: Required for Gemini AI API calls.
# AI Studio automatically injects this at runtime from user secrets.
# Users configure this via the Secrets panel in the AI Studio UI.
GEMINI_API_KEY="MY_GEMINI_API_KEY"
# APP_URL: The URL where this applet is hosted.
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
# Used for self-referential links, OAuth callbacks, and API endpoints.
APP_URL="MY_APP_URL"
+3 -1
View File
@@ -3,7 +3,9 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LiteCharms IT Services</title>
<title>Lite Charms</title>
<!-- SMTPJS direct mail client -->
<script src="https://smtpjs.com/v3/smtp.js" async></script>
</head>
<body>
<div id="root"></div>
-2
View File
@@ -18,7 +18,6 @@
"express": "^4.21.2",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"nodemailer": "^9.0.3",
"react": "^19.0.1",
"react-dom": "^19.0.1",
"vite": "^6.2.3"
@@ -26,7 +25,6 @@
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.14.0",
"@types/nodemailer": "^8.0.1",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.0",
"tailwindcss": "^4.1.14",
+37 -84
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,50 +12,27 @@ 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 = `
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>
@@ -84,85 +60,62 @@ async function startServer() {
</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-size: 13px; color: #1e293b;">${subject}</td>
</tr>
</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>
${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;">
${(estimateSummary.breakdown || []).map((b: any) => `<li>${b.item}: ${b.price}</li>`).join('')}
${breakdownHtml}
</ul>
</div>
` : ''}
</div>
`;
}
` : ''}
emailHtml += `
<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>
<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>
<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.
This is an automated dispatch from the LiteCharms website server.
</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
});
}
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',
},
});
await transporter.sendMail({
from: smtpConfig.fromEmail || smtpConfig.user,
to: smtpConfig.toEmail,
body: JSON.stringify({
htmlBody: emailHtml,
from: 'contact@litecharms.co.za',
to: 'contact@litecharms.co.za',
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 });
}
});
-11
View File
@@ -1,11 +0,0 @@
{
"host": "mail.litecharms.co.za",
"port": 465,
"secure": true,
"auth": {
"user": "contact@litecharms.co.za",
"pass": "PPUzCl%TL$GD*x7f"
},
"fromEmail": "LiteCharms <contact@khongisa.co.za>",
"toEmail": "contact@litecharms.co.za"
}
+11 -14
View File
@@ -7,6 +7,7 @@ import FAQSection from './components/FAQSection';
import AuroraBackground from './components/AuroraBackground';
import { BrandIcon, BrandLogo } from './components/BrandAssets';
import { Inquiry } from './types';
import { sendEmailClientSide } from './utils/emailController';
import {
MapPin,
Mail,
@@ -28,8 +29,8 @@ export default function App() {
const [contactLoading, setContactLoading] = useState(false);
// Stats Counters state for load-in effect
const [activeClients, setActiveClients] = useState(12);
const [completedProjects, setCompletedProjects] = useState(25);
const [activeClients, setActiveClients] = useState(1);
const [completedProjects, setCompletedProjects] = useState(5);
useEffect(() => {
// Soft animate stats for visual delight
@@ -39,9 +40,9 @@ export default function App() {
clearInterval(clientsTimer);
return 4;
}
return prev + 2;
return prev + 1;
});
}, 40);
}, 150);
const projectsTimer = setInterval(() => {
setCompletedProjects((prev) => {
@@ -49,9 +50,9 @@ export default function App() {
clearInterval(projectsTimer);
return 25;
}
return prev + 4;
return prev + 1;
});
}, 25);
}, 40);
return () => {
clearInterval(clientsTimer);
@@ -93,19 +94,15 @@ export default function App() {
localStorage.setItem('litecharms_general_contacts', JSON.stringify(pastMsgs));
try {
await fetch('/api/send-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
await sendEmailClientSide({
name: contactName,
email: contactEmail,
subject: contactSubject,
message: contactMessage,
isEstimate: false,
}),
});
} catch (err) {
console.error('SMTP API Submission failed. Saved locally instead:', err);
console.error('SMTP direct send failed. Saved locally instead:', err);
} finally {
setContactLoading(false);
setContactSuccess(true);
@@ -270,7 +267,7 @@ export default function App() {
LiteCharms (PTY) Ltd was born in Midrand in 2020, initially serving as a specialist interface consulting firm for the emerging mobile industry in South Africa. As local enterprise operations expanded, we observed a crucial bottleneck: software layout design cannot achieve its full potential if clients lack reliable, containerized hosting environments and flexible scale options.
</p>
<p>
In 2024, we expanded our core divisions to provide automated Docker container layouts, declarative Kubernetes blueprints, and cloud setups, powered by our high-performance private cloud platform, khongisa.co.za.
In 2022, we expanded our core divisions to provide automated Docker container layouts, declarative Kubernetes blueprints, and cloud setups, powered by our high-performance private cloud platform, khongisa.co.za.
</p>
<p className="font-medium text-white">
Today, we bridge the gap between user experience and modern cloud orchestration, delivering reliable structures that are optimized, secure, and fully documented.
@@ -281,7 +278,7 @@ export default function App() {
<div className="pt-6 border-t border-slate-800 space-y-3 text-xs">
<div className="flex items-center space-x-3 text-slate-300">
<MapPin className="w-4 h-4 text-brand-500 shrink-0" />
<span>1633 Liebenburg Rd, Noordwyk, Midrand, Gauteng, ZA</span>
<span>1633 Liebenburg Rd, Noordwyk, Midrand, 1687, South Africa</span>
</div>
<div className="flex items-center space-x-3 text-slate-300">
<Clock className="w-4 h-4 text-brand-500 shrink-0" />
+3 -3
View File
@@ -30,12 +30,12 @@ export default function FAQSection() {
className="w-full flex items-center justify-between p-6 text-left font-display font-semibold text-white hover:text-brand-400 transition-colors cursor-pointer"
>
<div className="flex items-center space-x-3.5 pr-4">
<HelpCircle className={`w-5 h-5 shrink-0 transition-colors ${isOpen ? 'text-brand-500' : 'text-slate-500'}`} />
<HelpCircle className={`w-5 h-5 shrink-0 transition-colors ${isOpen ? 'text-brand-400' : 'text-slate-400'}`} />
<span className="text-sm md:text-base leading-snug">{faq.question}</span>
</div>
<ChevronDown
className={`w-4 h-4 text-slate-500 shrink-0 transition-transform duration-300 ${
isOpen ? 'rotate-180 text-brand-500' : ''
className={`w-4 h-4 text-slate-400 shrink-0 transition-transform duration-300 ${
isOpen ? 'rotate-180 text-brand-400' : ''
}`}
/>
</button>
+20 -9
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, FormEvent } from 'react';
import { useState, useEffect, FormEvent, useRef } from 'react';
import { Inquiry } from '../types';
import { sendEmailClientSide } from '../utils/emailController';
import { Calculator, Sparkles, Send, ShieldCheck, CheckCircle2, History, Trash2 } from 'lucide-react';
interface ProjectEstimatorProps {
@@ -43,6 +44,20 @@ export default function ProjectEstimator({ onInquirySubmitted }: ProjectEstimato
}
}, []);
const isMounted = useRef(false);
// Scroll to estimator top when step or success state changes
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
return;
}
const element = document.getElementById('estimator');
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, [step, success]);
// Compute estimate values
const calculateEstimate = () => {
// Base prices (Min, Max) in ZAR
@@ -197,10 +212,7 @@ export default function ProjectEstimator({ onInquirySubmitted }: ProjectEstimato
onInquirySubmitted(newInquiry);
try {
await fetch('/api/send-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
await sendEmailClientSide({
name,
email,
phone,
@@ -214,7 +226,6 @@ export default function ProjectEstimator({ onInquirySubmitted }: ProjectEstimato
timelineWeeks: estimate.timelineWeeks,
breakdown: estimate.breakdown,
},
}),
});
} catch (err) {
console.error('SMTP Proposal send failed. Saved locally:', err);
@@ -536,7 +547,7 @@ export default function ProjectEstimator({ onInquirySubmitted }: ProjectEstimato
<span className="font-display text-3xl md:text-4xl font-extrabold text-brand-500">
R {estimate.totalMin.toLocaleString('en-ZA')}
</span>
<span className="text-slate-500 font-medium text-xs">to</span>
<span className="text-slate-400 font-medium text-xs">to</span>
<span className="font-display text-2xl md:text-3xl font-extrabold text-accent-400">
R {estimate.totalMax.toLocaleString('en-ZA')}
</span>
@@ -549,7 +560,7 @@ export default function ProjectEstimator({ onInquirySubmitted }: ProjectEstimato
{/* Key details */}
<div className="grid grid-cols-2 gap-4 py-4 border-y border-slate-800">
<div>
<p className="text-[9px] font-mono text-slate-500 uppercase tracking-wider">
<p className="text-[9px] font-mono text-slate-300 uppercase tracking-wider">
Completion Roadmap
</p>
<p className="font-bold text-sm text-white mt-1">
@@ -557,7 +568,7 @@ export default function ProjectEstimator({ onInquirySubmitted }: ProjectEstimato
</p>
</div>
<div>
<p className="text-[9px] font-mono text-slate-500 uppercase tracking-wider">
<p className="text-[9px] font-mono text-slate-300 uppercase tracking-wider">
Priority Mode
</p>
<p className="font-bold text-sm text-brand-500 mt-1 capitalize">
+1 -13
View File
@@ -138,24 +138,12 @@ export const TIMELINE: TimelineEvent[] = [
},
{
year: '2022',
title: 'Multi-Platform Pivot',
description: 'Expanded our services to full web and desktop software layout design, securing major clients in Gauteng\'s logistics and retail sectors.',
milestone: false
},
{
year: '2023',
title: 'Private Cloud R&D',
description: 'In response to clients facing local server limitations, we began research into dedicated hypervisors, virtualization technologies, and private network clusters.',
milestone: false
},
{
year: '2024',
title: 'Khongisa Cloud Platform Launch',
description: 'Successfully built and rolled out our proprietary private cloud hosting platform (khongisa.co.za), allowing client applications to host seamlessly without third-party servers.',
milestone: true
},
{
year: '2025',
year: '2024',
title: 'Kubernetes & IaC Integration',
description: 'Expanded our operations to offer enterprise-grade Docker containerization, Kubernetes configuration blueprints, and multi-cloud Infrastructure as Code (IaC) designs.',
milestone: true
+8
View File
@@ -71,6 +71,14 @@
}
/* Base custom classes */
html, body {
font-family: var(--font-sans);
}
button, input, select, textarea {
font-family: inherit;
}
.font-display {
font-family: var(--font-display);
}
+147
View File
@@ -0,0 +1,147 @@
import { Inquiry } from '../types';
export interface EmailData {
name: string;
email: string;
phone?: string;
company?: string;
subject: string;
message: string;
isEstimate: boolean;
estimateSummary?: {
totalMin: number;
totalMax: number;
timelineWeeks: number;
breakdown: Array<{ item: string; price: string }>;
};
}
// Dynamically load the SMTPJS script to ensure it is always available
export function loadSmtpScript(): Promise<void> {
return Promise.resolve();
}
/**
* Handles sending emails directly via the external messenger API.
* This completely avoids the need for local SMTP secrets, PHP files, or internal proxying.
*/
export async function sendEmailClientSide(data: EmailData): Promise<{ success: boolean; message: string }> {
try {
const name = data.name.trim();
const email = data.email.trim();
const phone = data.phone?.trim() || 'Not Provided';
const company = data.company?.trim() || 'Not Provided';
const subject = data.subject?.trim() || 'General Inquiry';
const message = data.message?.trim() || 'No additional notes provided.';
const isEstimate = data.isEstimate;
const estimateSummary = data.estimateSummary;
// Generate Email Subject line
const emailSubject = isEstimate
? `LiteCharms Project Estimate Proposal: ${subject}`
: `LiteCharms General Service Inquiry: ${subject}`;
// Build breakdown items list for estimate summary
let breakdownHtml = '';
if (isEstimate && estimateSummary && Array.isArray(estimateSummary.breakdown)) {
breakdownHtml = estimateSummary.breakdown
.map(b => `<li>${b.item}: ${b.price}</li>`)
.join('');
}
// Compose professional HTML Email template
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>
</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>
</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}</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}</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}</td>
</tr>
</table>
${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>
${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>
` : ''}
<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.replace(/\n/g, '<br/>')}</p>
</div>
</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 client.
</div>
</div>
`;
// Construct request body for messenger API
const requestBody = {
htmlBody: emailHtml,
from: 'contact@litecharms.co.za', // Default authorized sender from domain
to: 'contact@litecharms.co.za', // Recipient address
subject: emailSubject
};
const response = await fetch('https://messenger.api.khongisa.co.za/api/send-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (response.ok) {
return {
success: true,
message: 'Your inquiry has been submitted successfully! We will get in touch soon.',
};
} else {
const errorText = await response.text();
throw new Error(errorText || `Server responded with status ${response.status}`);
}
} catch (error: any) {
console.error('Messenger API send error:', error);
return {
success: false,
message: error?.message || 'Failed to dispatch email via Messenger API. Please verify your connection or email us directly at contact@litecharms.co.za',
};
}
}