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 { checkRateLimit, getRateLimitHeaders, getClientIp, requireSessionAuth } from "@/lib/auth";
const B = {
siteUrl: "https://dk0.dev",
email: "contact@dk0.dev",
mint: "#A7F3D0",
sky: "#BAE6FD",
purple: "#E9D5FF",
red: "#EF4444",
};
function escapeHtml(input: string): string {
return input
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function nl2br(input: string): string {
return escapeHtml(input).replace(/\r\n|\r|\n/g, "
");
}
function baseEmail(opts: { title: string; preheader: string; bodyHtml: string }): string {
const sentAt = new Date().toLocaleString("de-DE", {
year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit",
});
return `
${escapeHtml(opts.title)}
${escapeHtml(opts.preheader)} · ${sentAt}
${escapeHtml(opts.title)}
dk0.dev
${opts.bodyHtml}
`;
}
function messageCard(label: string, html: string, accentColor: string = B.mint): string {
return `
`;
}
function ctaButton(text: string, href: string): string {
return `
`;
}
const emailTemplates = {
welcome: {
subject: "Vielen Dank für deine Nachricht! 👋",
template: (name: string, originalMessage: string) => {
const safeName = escapeHtml(name);
return baseEmail({
title: `Danke, ${safeName}!`,
preheader: "Nachricht erhalten",
bodyHtml: `
Hey ${safeName},
danke für deine Nachricht — ich habe sie erhalten und melde mich so schnell wie möglich bei dir zurück. 🙌
${messageCard("Deine Nachricht", nl2br(originalMessage))}
${ctaButton("Portfolio ansehen →", B.siteUrl)}`,
});
},
},
project: {
subject: "Projekt-Anfrage erhalten! 🚀",
template: (name: string, originalMessage: string) => {
const safeName = escapeHtml(name);
return baseEmail({
title: `Projekt-Anfrage: danke, ${safeName}!`,
preheader: "Ich melde mich zeitnah",
bodyHtml: `
Hey ${safeName},
mega — danke für die Projekt-Anfrage! Ich schaue mir alles an und melde mich bald mit Ideen und Rückfragen. 🚀
${messageCard("Deine Projekt-Anfrage", nl2br(originalMessage), B.sky)}
${ctaButton("Mein Portfolio ansehen →", B.siteUrl)}`,
});
},
},
quick: {
subject: "Danke für deine Nachricht! ⚡",
template: (name: string, originalMessage: string) => {
const safeName = escapeHtml(name);
return baseEmail({
title: `Danke, ${safeName}!`,
preheader: "Kurze Bestätigung",
bodyHtml: `
Hey ${safeName},
kurze Bestätigung: deine Nachricht ist angekommen. Ich melde mich bald zurück. ⚡
${messageCard("Deine Nachricht", nl2br(originalMessage))}`,
});
},
},
reply: {
subject: "Antwort auf deine Nachricht 📧",
template: (name: string, originalMessage: string, responseMessage: string) => {
const safeName = escapeHtml(name);
return baseEmail({
title: `Hey ${safeName}!`,
preheader: "Antwort von Dennis",
bodyHtml: `
Hey ${safeName},
ich habe mir deine Nachricht angeschaut — hier ist meine Antwort:
${messageCard("Antwort von Dennis", nl2br(responseMessage), B.mint)}
${messageCard("Deine ursprüngliche Nachricht", nl2br(originalMessage), "#2a2a2a")}
${ctaButton("Portfolio ansehen →", B.siteUrl)}`,
});
},
},
};
export async function POST(request: NextRequest) {
try {
const isAdminRequest = request.headers.get("x-admin-request") === "true";
if (!isAdminRequest) return NextResponse.json({ error: "Admin access required" }, { status: 403 });
const authError = requireSessionAuth(request);
if (authError) return authError;
const ip = getClientIp(request);
if (!checkRateLimit(ip, 10, 60000)) {
return NextResponse.json(
{ error: "Rate limit exceeded" },
{ status: 429, headers: { ...getRateLimitHeaders(ip, 10, 60000) } },
);
}
const body = (await request.json()) as {
to: string;
name: string;
template: 'welcome' | 'project' | 'quick' | 'reply';
originalMessage: string;
response?: string;
};
const { to, name, template, originalMessage, response } = body;
if (!to || !name || !template || !originalMessage) {
return NextResponse.json({ error: "Alle Felder sind erforderlich" }, { status: 400 });
}
if (template === "reply" && (!response || !response.trim())) {
return NextResponse.json({ error: "Antworttext ist erforderlich" }, { status: 400 });
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(to)) {
return NextResponse.json({ error: "Ungültige E-Mail-Adresse" }, { status: 400 });
}
if (!emailTemplates[template]) {
return NextResponse.json({ error: "Ungültiges Template" }, { status: 400 });
}
const user = process.env.MY_EMAIL ?? "";
const pass = process.env.MY_PASSWORD ?? "";
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,
requireTLS: true,
auth: { type: "login", user, pass },
connectionTimeout: 30000,
greetingTimeout: 30000,
socketTimeout: 60000,
tls: { rejectUnauthorized: false, ciphers: 'SSLv3' },
};
const transport = nodemailer.createTransport(transportOptions);
try {
await transport.verify();
} catch {
return NextResponse.json({ error: "E-Mail-Server-Verbindung fehlgeschlagen" }, { status: 500 });
}
const selectedTemplate = emailTemplates[template];
const html = template === "reply"
? emailTemplates.reply.template(name, originalMessage, response || "")
: emailTemplates[template as Exclude].template(name, originalMessage);
const mailOptions: Mail.Options = {
from: `"Dennis Konkol" <${user}>`,
to,
replyTo: B.email,
subject: selectedTemplate.subject,
html,
text: template === "reply"
? `Hey ${name}!\n\nAntwort:\n${response}\n\nDeine ursprüngliche Nachricht:\n${originalMessage}\n\n-- Dennis Konkol\n${B.siteUrl}`
: `Hey ${name}!\n\nDanke für deine Nachricht:\n${originalMessage}\n\nIch melde mich bald!\n\n-- Dennis Konkol\n${B.siteUrl}`,
};
const result = await new Promise((resolve, reject) => {
transport.sendMail(mailOptions, (err, info) => {
if (!err) resolve(info.response);
else reject(err.message);
});
});
return NextResponse.json({ message: "E-Mail erfolgreich gesendet", template, messageId: result });
} catch (err) {
return NextResponse.json({
error: "Fehler beim Senden der E-Mail",
details: err instanceof Error ? err.message : 'Unbekannter Fehler',
}, { status: 500 });
}
}