diff --git a/src/pages/api/contact.ts b/src/pages/api/contact.ts index 90b6971..2e9ff86 100644 --- a/src/pages/api/contact.ts +++ b/src/pages/api/contact.ts @@ -90,6 +90,60 @@ function validateContactForm(data: ContactFormData): string | null { return null; } +// ============================================================ +// SMTP transporter (created once at module level for reuse) +// ============================================================ +let smtpTransporter: any = null; +let smtpVerified = false; + +// Verify SMTP connection once at module initialization +(async () => { + const smtpHost = process.env.SMTP_HOST; + const smtpUser = process.env.SMTP_USER; + const smtpPass = process.env.SMTP_PASS; + const smtpPort = parseInt(process.env.SMTP_PORT ?? '587', 10); + + if (!smtpHost || !smtpUser || !smtpPass) { + logger.info('api.contact', 'SMTP not configured — skipping connection verification'); + return; + } + + try { + const nodemailer = await import('nodemailer'); + smtpTransporter = nodemailer.default.createTransport({ + host: smtpHost, + port: smtpPort, + secure: false, // Port 587 uses STARTTLS, not SSL + auth: { user: smtpUser, pass: smtpPass }, + requireTLS: true, // Force STARTTLS upgrade + tls: { + rejectUnauthorized: false, // Handle Hostinger certificate issues + }, + }); + + await smtpTransporter.verify(); + smtpVerified = true; + console.log('SMTP connection verified successfully'); + logger.info('api.contact', 'SMTP connection verified successfully', { + host: smtpHost, + port: smtpPort, + user: smtpUser, + }); + } catch (err: any) { + smtpVerified = false; + console.error('SMTP connection verification failed:', { + code: err?.code, + message: err?.message, + response: err?.response, + }); + logger.error('api.contact', 'SMTP connection verification failed', { + code: err?.code, + message: err?.message, + response: err?.response, + }); + } +})(); + // ============================================================ // Email sending via SMTP (nodemailer) or no-op in development // ============================================================ @@ -113,17 +167,20 @@ async function sendContactEmail(data: ContactFormData): Promise { // Dynamic import to avoid bundling issues when nodemailer isn't installed try { - const nodemailer = await import('nodemailer'); - const transporter = nodemailer.default.createTransport({ - host: smtpHost, - port: smtpPort, - secure: false, // Port 587 uses STARTTLS, not SSL - auth: { user: smtpUser, pass: smtpPass }, - requireTLS: true, // Force STARTTLS upgrade - tls: { - rejectUnauthorized: false, // Handle Hostinger certificate issues - }, - }); + // Reuse module-level transporter if available, otherwise create a new one + if (!smtpTransporter) { + const nodemailer = await import('nodemailer'); + smtpTransporter = nodemailer.default.createTransport({ + host: smtpHost, + port: smtpPort, + secure: false, + auth: { user: smtpUser, pass: smtpPass }, + requireTLS: true, + tls: { + rejectUnauthorized: false, + }, + }); + } const subjectLabels: Record = { 'web-development': 'Web Development', @@ -137,7 +194,7 @@ async function sendContactEmail(data: ContactFormData): Promise { const fromAddress = smtpFrom || smtpUser; - await transporter.sendMail({ + const info = await smtpTransporter.sendMail({ from: `"CompanySite Contact Form" <${fromAddress}>`, to: recipientEmail, replyTo: data.email, @@ -168,6 +225,8 @@ async function sendContactEmail(data: ContactFormData): Promise {

Submitted at: ${new Date().toISOString()}

`, }); + + return info; } catch (err) { // If nodemailer isn't installed, treat as graceful degradation (log only) if (err instanceof Error && err.message.includes('Cannot find module')) { @@ -314,24 +373,47 @@ export const POST: APIRoute = async ({ request, clientAddress }) => { const recipientEmail = process.env.CONTACT_EMAIL_TO || 'unknown'; console.log('Sending contact email to: ' + recipientEmail); - sendContactEmail(formData).catch(async (err) => { - console.error('Background email delivery failed:', { - recipient: recipientEmail, - email: formData.email, - subject: formData.subject, - error: err instanceof Error ? err.message : String(err), - stack: err instanceof Error ? err.stack : undefined, + sendContactEmail(formData) + .then((info) => { + console.log('Contact email delivered successfully to:', recipientEmail, { + messageId: info?.messageId, + }); + logger.info('api.contact', 'Contact email delivered successfully', { + recipient: recipientEmail, + messageId: info?.messageId, + from: formData.email, + subject: formData.subject, + }); + }) + .catch(async (err: any) => { + console.error('Background email delivery failed:', { + code: err?.code, + message: err?.message, + response: err?.response, + responseCode: err?.responseCode, + recipient: recipientEmail, + email: formData.email, + subject: formData.subject, + }); + logger.error('api.contact', 'Background email delivery failed', { + code: err?.code, + message: err?.message, + response: err?.response, + responseCode: err?.responseCode, + email: formData.email, + subject: formData.subject, + }); + await captureException(err, { + scope: 'api.contact.background', + extra: { + email: formData.email, + subject: formData.subject, + smtpCode: err?.code, + smtpResponse: err?.response, + smtpResponseCode: err?.responseCode, + }, + }); }); - logger.error('api.contact', 'Background email delivery failed', { - email: formData.email, - subject: formData.subject, - error: err instanceof Error ? err.message : String(err), - }); - await captureException(err, { - scope: 'api.contact.background', - extra: { email: formData.email, subject: formData.subject }, - }); - }); logApiRequest({ scope: 'api.contact',