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; 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 // 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 // Dynamic import to avoid bundling issues when nodemailer isn't installed
try { try {
const nodemailer = await import('nodemailer'); // Reuse module-level transporter if available, otherwise create a new one
const transporter = nodemailer.default.createTransport({ if (!smtpTransporter) {
host: smtpHost, const nodemailer = await import('nodemailer');
port: smtpPort, smtpTransporter = nodemailer.default.createTransport({
secure: false, // Port 587 uses STARTTLS, not SSL host: smtpHost,
auth: { user: smtpUser, pass: smtpPass }, port: smtpPort,
requireTLS: true, // Force STARTTLS upgrade secure: false,
tls: { auth: { user: smtpUser, pass: smtpPass },
rejectUnauthorized: false, // Handle Hostinger certificate issues requireTLS: true,
}, tls: {
}); rejectUnauthorized: false,
},
});
}
const subjectLabels: Record<string, string> = { const subjectLabels: Record<string, string> = {
'web-development': 'Web Development', 'web-development': 'Web Development',
@@ -137,7 +194,7 @@ async function sendContactEmail(data: ContactFormData): Promise<void> {
const fromAddress = smtpFrom || smtpUser; const fromAddress = smtpFrom || smtpUser;
await transporter.sendMail({ const info = await smtpTransporter.sendMail({
from: `"CompanySite Contact Form" <${fromAddress}>`, from: `"CompanySite Contact Form" <${fromAddress}>`,
to: recipientEmail, to: recipientEmail,
replyTo: data.email, 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> <p style="color:#888;font-size:12px;margin-top:16px">Submitted at: ${new Date().toISOString()}</p>
`, `,
}); });
return info;
} catch (err) { } catch (err) {
// If nodemailer isn't installed, treat as graceful degradation (log only) // If nodemailer isn't installed, treat as graceful degradation (log only)
if (err instanceof Error && err.message.includes('Cannot find module')) { 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'; const recipientEmail = process.env.CONTACT_EMAIL_TO || 'unknown';
console.log('Sending contact email to: ' + recipientEmail); console.log('Sending contact email to: ' + recipientEmail);
sendContactEmail(formData).catch(async (err) => { sendContactEmail(formData)
console.error('Background email delivery failed:', { .then((info) => {
recipient: recipientEmail, console.log('Contact email delivered successfully to:', recipientEmail, {
email: formData.email, messageId: info?.messageId,
subject: formData.subject, });
error: err instanceof Error ? err.message : String(err), logger.info('api.contact', 'Contact email delivered successfully', {
stack: err instanceof Error ? err.stack : undefined, 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({ logApiRequest({
scope: 'api.contact', scope: 'api.contact',