feat: add SMTP connection verification and detailed delivery logging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ajay Ghanwat
2026-04-13 12:58:02 +05:30
co-authored by Claude Opus 4.6
parent 48058c544e
commit 40ed89793b
+111 -29
View File
@@ -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<void> {
// 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<string, string> = {
'web-development': 'Web Development',
@@ -137,7 +194,7 @@ async function sendContactEmail(data: ContactFormData): Promise<void> {
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<void> {
<p style="color:#888;font-size:12px;margin-top:16px">Submitted at: ${new Date().toISOString()}</p>
`,
});
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',