* update * cleanup * fixing linting and tests errors * Refactor API Parameter Handling and Update Email Transport ✅ Updated API Route Parameters: - Changed parameter type from `{ id: string }` to `Promise<{ id: string }>` in PUT and DELETE methods for better async handling. ✅ Fixed Email Transport Creation: - Updated `nodemailer.createTransporter` to `nodemailer.createTransport` for correct transport configuration. ✅ Refactored AnalyticsDashboard Component: - Changed export from default to named export for better modularity. ✅ Enhanced Email Responder Toast: - Updated toast structure to include additional properties for better user feedback. 🎯 Overall Improvements: - Improved async handling in API routes. - Ensured correct usage of nodemailer. - Enhanced component exports and user notifications.
286 lines
14 KiB
TypeScript
286 lines
14 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server";
|
|
import nodemailer from "nodemailer";
|
|
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
|
import Mail from "nodemailer/lib/mailer";
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const body = (await request.json()) as {
|
|
email: string;
|
|
name: string;
|
|
subject: string;
|
|
message: string;
|
|
};
|
|
const { email, name, subject, message } = body;
|
|
|
|
console.log('📧 Email request received:', { email, name, subject, messageLength: message.length });
|
|
|
|
// Validate input
|
|
if (!email || !name || !subject || !message) {
|
|
console.error('❌ Validation failed: Missing required fields');
|
|
return NextResponse.json(
|
|
{ error: "Alle Felder sind erforderlich" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// Validate email format
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email)) {
|
|
console.error('❌ Validation failed: Invalid email format');
|
|
return NextResponse.json(
|
|
{ error: "Ungültige E-Mail-Adresse" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// Validate message length
|
|
if (message.length < 10) {
|
|
console.error('❌ Validation failed: Message too short');
|
|
return NextResponse.json(
|
|
{ error: "Nachricht muss mindestens 10 Zeichen lang sein" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const user = process.env.MY_EMAIL ?? "";
|
|
const pass = process.env.MY_PASSWORD ?? "";
|
|
|
|
console.log('🔑 Environment check:', {
|
|
hasEmail: !!user,
|
|
hasPassword: !!pass,
|
|
emailHost: user.split('@')[1] || 'unknown'
|
|
});
|
|
|
|
if (!user || !pass) {
|
|
console.error("❌ Missing email/password environment variables");
|
|
return NextResponse.json(
|
|
{ error: "E-Mail-Server nicht konfiguriert" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
|
|
const transportOptions: SMTPTransport.Options = {
|
|
host: "mail.dk0.dev",
|
|
port: 587,
|
|
secure: false, // Port 587 uses STARTTLS, not SSL/TLS
|
|
requireTLS: true,
|
|
auth: {
|
|
type: "login",
|
|
user,
|
|
pass,
|
|
},
|
|
// Increased timeout settings for better reliability
|
|
connectionTimeout: 30000, // 30 seconds
|
|
greetingTimeout: 30000, // 30 seconds
|
|
socketTimeout: 60000, // 60 seconds
|
|
// Additional TLS options for better compatibility
|
|
tls: {
|
|
rejectUnauthorized: false, // Allow self-signed certificates
|
|
ciphers: 'SSLv3'
|
|
}
|
|
};
|
|
|
|
console.log('🚀 Creating transport with options:', {
|
|
host: transportOptions.host,
|
|
port: transportOptions.port,
|
|
secure: transportOptions.secure,
|
|
user: user.split('@')[0] + '@***' // Hide full email in logs
|
|
});
|
|
|
|
const transport = nodemailer.createTransport(transportOptions);
|
|
|
|
// Verify transport configuration with retry logic
|
|
let verificationAttempts = 0;
|
|
const maxVerificationAttempts = 3;
|
|
let verificationSuccess = false;
|
|
|
|
while (verificationAttempts < maxVerificationAttempts && !verificationSuccess) {
|
|
try {
|
|
verificationAttempts++;
|
|
console.log(`🔍 SMTP verification attempt ${verificationAttempts}/${maxVerificationAttempts}`);
|
|
await transport.verify();
|
|
console.log('✅ SMTP connection verified successfully');
|
|
verificationSuccess = true;
|
|
} catch (verifyError) {
|
|
console.error(`❌ SMTP verification attempt ${verificationAttempts} failed:`, verifyError);
|
|
|
|
if (verificationAttempts >= maxVerificationAttempts) {
|
|
console.error('❌ All SMTP verification attempts failed');
|
|
return NextResponse.json(
|
|
{ error: "E-Mail-Server-Verbindung fehlgeschlagen" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
|
|
// Wait before retry
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
}
|
|
}
|
|
|
|
const mailOptions: Mail.Options = {
|
|
from: `"Portfolio Contact" <${user}>`,
|
|
to: "contact@dk0.dev", // Send to your contact email
|
|
replyTo: email,
|
|
subject: `Portfolio Kontakt: ${subject}`,
|
|
html: `
|
|
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Neue Kontaktanfrage - Portfolio</title>
|
|
</head>
|
|
<body style="margin: 0; padding: 0; background-color: #f8fafc; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">
|
|
<div style="max-width: 600px; margin: 0 auto; background-color: #ffffff; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
|
|
|
<!-- Header -->
|
|
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px 30px; text-align: center;">
|
|
<h1 style="color: #ffffff; margin: 0; font-size: 28px; font-weight: 600; letter-spacing: -0.5px;">
|
|
📧 Neue Kontaktanfrage
|
|
</h1>
|
|
<p style="color: #e2e8f0; margin: 8px 0 0 0; font-size: 16px; opacity: 0.9;">
|
|
Von deinem Portfolio
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div style="padding: 40px 30px;">
|
|
|
|
<!-- Contact Info Card -->
|
|
<div style="background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); padding: 30px; border-radius: 12px; margin-bottom: 30px; border: 1px solid #e2e8f0;">
|
|
<div style="display: flex; align-items: center; margin-bottom: 20px;">
|
|
<div style="width: 50px; height: 50px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-right: 15px;">
|
|
<span style="color: #ffffff; font-size: 20px; font-weight: bold;">${name.charAt(0).toUpperCase()}</span>
|
|
</div>
|
|
<div>
|
|
<h2 style="color: #1e293b; margin: 0; font-size: 24px; font-weight: 600;">${name}</h2>
|
|
<p style="color: #64748b; margin: 4px 0 0 0; font-size: 14px;">Kontaktanfrage</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
|
|
<div style="background: #ffffff; padding: 20px; border-radius: 8px; border-left: 4px solid #10b981;">
|
|
<h4 style="color: #059669; margin: 0 0 8px 0; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">E-Mail</h4>
|
|
<p style="color: #374151; margin: 0; font-size: 16px; font-weight: 500;">${email}</p>
|
|
</div>
|
|
<div style="background: #ffffff; padding: 20px; border-radius: 8px; border-left: 4px solid #3b82f6;">
|
|
<h4 style="color: #2563eb; margin: 0 0 8px 0; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">Betreff</h4>
|
|
<p style="color: #374151; margin: 0; font-size: 16px; font-weight: 500;">${subject}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Card -->
|
|
<div style="background: #ffffff; padding: 30px; border-radius: 12px; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);">
|
|
<div style="display: flex; align-items: center; margin-bottom: 20px;">
|
|
<div style="width: 8px; height: 8px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; margin-right: 12px;"></div>
|
|
<h3 style="color: #1e293b; margin: 0; font-size: 18px; font-weight: 600;">Nachricht</h3>
|
|
</div>
|
|
<div style="background: #f8fafc; padding: 25px; border-radius: 8px; border-left: 4px solid #667eea;">
|
|
<p style="color: #374151; margin: 0; line-height: 1.7; font-size: 16px; white-space: pre-wrap;">${message}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Button -->
|
|
<div style="text-align: center; margin-top: 30px;">
|
|
<a href="mailto:${email}?subject=Re: ${subject}" style="display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; text-decoration: none; padding: 15px 30px; border-radius: 8px; font-weight: 600; font-size: 16px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); transition: all 0.2s;">
|
|
📬 Antworten
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div style="background: #f8fafc; padding: 30px; text-align: center; border-top: 1px solid #e2e8f0;">
|
|
<div style="margin-bottom: 15px;">
|
|
<span style="display: inline-block; width: 40px; height: 2px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 1px;"></span>
|
|
</div>
|
|
<p style="color: #64748b; margin: 0; font-size: 14px; line-height: 1.5;">
|
|
Diese E-Mail wurde automatisch von deinem Portfolio generiert.<br>
|
|
<strong>Dennis Konkol Portfolio</strong> • <a href="https://dki.one" style="color: #667eea; text-decoration: none;">dki.one</a>
|
|
</p>
|
|
<p style="color: #94a3b8; margin: 10px 0 0 0; font-size: 12px;">
|
|
${new Date().toLocaleString('de-DE', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</p>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`,
|
|
text: `
|
|
Neue Kontaktanfrage von deinem Portfolio
|
|
|
|
Von: ${name} (${email})
|
|
Betreff: ${subject}
|
|
|
|
Nachricht:
|
|
${message}
|
|
|
|
---
|
|
Diese E-Mail wurde automatisch von deinem Portfolio generiert.
|
|
`,
|
|
};
|
|
|
|
console.log('📤 Sending email...');
|
|
|
|
// Email sending with retry logic
|
|
let sendAttempts = 0;
|
|
const maxSendAttempts = 3;
|
|
let sendSuccess = false;
|
|
let result = '';
|
|
|
|
while (sendAttempts < maxSendAttempts && !sendSuccess) {
|
|
try {
|
|
sendAttempts++;
|
|
console.log(`📤 Email send attempt ${sendAttempts}/${maxSendAttempts}`);
|
|
|
|
const sendMailPromise = () =>
|
|
new Promise<string>((resolve, reject) => {
|
|
transport.sendMail(mailOptions, function (err, info) {
|
|
if (!err) {
|
|
console.log('✅ Email sent successfully:', info.response);
|
|
resolve(info.response);
|
|
} else {
|
|
console.error("❌ Error sending email:", err);
|
|
reject(err.message);
|
|
}
|
|
});
|
|
});
|
|
|
|
result = await sendMailPromise();
|
|
sendSuccess = true;
|
|
console.log('🎉 Email process completed successfully');
|
|
} catch (sendError) {
|
|
console.error(`❌ Email send attempt ${sendAttempts} failed:`, sendError);
|
|
|
|
if (sendAttempts >= maxSendAttempts) {
|
|
console.error('❌ All email send attempts failed');
|
|
throw new Error(`Failed to send email after ${maxSendAttempts} attempts: ${sendError}`);
|
|
}
|
|
|
|
// Wait before retry
|
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({
|
|
message: "E-Mail erfolgreich gesendet",
|
|
messageId: result
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error("❌ Unexpected error in email API:", err);
|
|
return NextResponse.json({
|
|
error: "Fehler beim Senden der E-Mail",
|
|
details: err instanceof Error ? err.message : 'Unbekannter Fehler'
|
|
}, { status: 500 });
|
|
}
|
|
}
|