import { type NextRequest, NextResponse } from "next/server"; import nodemailer from "nodemailer"; import SMTPTransport from "nodemailer/lib/smtp-transport"; import Mail from "nodemailer/lib/mailer"; import { PrismaClient } from '@prisma/client'; import { checkRateLimit, getRateLimitHeaders } from '@/lib/auth'; const prisma = new PrismaClient(); // Sanitize input to prevent XSS function sanitizeInput(input: string, maxLength: number = 10000): string { return input .slice(0, maxLength) .replace(/[<>]/g, '') // Remove potential HTML tags .trim(); } export async function POST(request: NextRequest) { try { // Rate limiting (defensive: headers may be undefined in tests) const ip = request.headers?.get?.('x-forwarded-for') ?? request.headers?.get?.('x-real-ip') ?? 'unknown'; if (!checkRateLimit(ip, 5, 60000)) { // 5 emails per minute per IP return NextResponse.json( { error: 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.' }, { status: 429, headers: { 'Content-Type': 'application/json', ...getRateLimitHeaders(ip, 5, 60000) } } ); } const body = (await request.json()) as { email: string; name: string; subject: string; message: string; }; // Sanitize and validate input const email = sanitizeInput(body.email || '', 255); const name = sanitizeInput(body.name || '', 100); const subject = sanitizeInput(body.subject || '', 200); const message = sanitizeInput(body.message || '', 5000); // Email request received // 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 }, ); } // Validate field lengths if (name.length > 100 || subject.length > 200 || message.length > 5000) { return NextResponse.json( { error: "Eingabe zu lang" }, { 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' } }; // Creating transport with configured options 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++; await transport.verify(); verificationSuccess = true; } catch (verifyError) { if (process.env.NODE_ENV === 'development') { console.error(`SMTP verification attempt ${verificationAttempts} failed:`, verifyError); } if (verificationAttempts >= maxVerificationAttempts) { if (process.env.NODE_ENV === 'development') { 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: ` Neue Kontaktanfrage - Portfolio

📧 Neue Kontaktanfrage

Von deinem Portfolio

${name.charAt(0).toUpperCase()}

${name}

Kontaktanfrage

E-Mail

${email}

Betreff

${subject}

Nachricht

${message}

📬 Antworten

Diese E-Mail wurde automatisch von deinem Portfolio generiert.
Dennis Konkol Portfoliodki.one

${new Date().toLocaleString('de-DE', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}

`, text: ` Neue Kontaktanfrage von deinem Portfolio Von: ${name} (${email}) Betreff: ${subject} Nachricht: ${message} --- Diese E-Mail wurde automatisch von deinem Portfolio generiert. `, }; // Sending email // Email sending with retry logic let sendAttempts = 0; const maxSendAttempts = 3; let sendSuccess = false; let result = ''; while (sendAttempts < maxSendAttempts && !sendSuccess) { try { sendAttempts++; // Email send attempt const sendMailPromise = () => new Promise((resolve, reject) => { transport.sendMail(mailOptions, function (err, info) { if (!err) { // Email sent successfully resolve(info.response); } else { if (process.env.NODE_ENV === 'development') { console.error("Error sending email:", err); } reject(err.message); } }); }); result = await sendMailPromise(); sendSuccess = true; // Email process completed successfully } catch (sendError) { if (process.env.NODE_ENV === 'development') { console.error(`Email send attempt ${sendAttempts} failed:`, sendError); } if (sendAttempts >= maxSendAttempts) { if (process.env.NODE_ENV === 'development') { 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)); } } // Save contact to database try { await prisma.contact.create({ data: { name, email, subject, message, responded: false } }); // Contact saved to database } catch (dbError) { if (process.env.NODE_ENV === 'development') { console.error('Error saving contact to database:', dbError); } // Don't fail the email send if DB save fails } 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 }); } }