feat: add SMTP connection verification and detailed delivery logging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
48058c544e
commit
40ed89793b
+111
-29
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user