import { NextResponse } from "next/server"; import { getPayload } from "payload"; import config from "@payload-config"; export const dynamic = "force-dynamic"; type InboundBody = { from?: unknown; sender?: unknown; email?: unknown; name?: unknown; subject?: unknown; text?: unknown; body?: unknown; html?: unknown; messageId?: unknown; inReplyTo?: unknown; }; function stringValue(value: unknown): string { return typeof value === "string" ? value.trim() : ""; } function extractEmail(value: unknown): string { if (value && typeof value === "object" && "email" in value) return extractEmail((value as { email?: unknown }).email); const raw = stringValue(value); const match = raw.match(/<([^>]+)>/) || raw.match(/([\w.+-]+@[\w.-]+\.[A-Za-z]{2,})/); return (match?.[1] || raw).trim().toLowerCase(); } function stripHtml(value: string): string { return value.replace(//gi, "").replace(//gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim(); } function findThreadKey(subject: string): string | null { return subject.match(/\[ANOUMA-([^\]]+)\]/i)?.[1] || null; } export async function POST(request: Request) { const secret = process.env.INBOUND_EMAIL_SECRET; const supplied = request.headers.get("x-inbound-secret") || request.headers.get("authorization")?.replace(/^Bearer\s+/i, ""); if (!secret || supplied !== secret) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); let body: InboundBody; try { body = await request.json(); } catch { return NextResponse.json({ error: "JSON erwartet." }, { status: 400 }); } const fromRaw = body.from ?? body.sender ?? body.email; const email = extractEmail(fromRaw); const fromName = fromRaw && typeof fromRaw === "object" && "name" in fromRaw ? stringValue((fromRaw as { name?: unknown }).name) : ""; const name = stringValue(body.name) || fromName || stringValue(fromRaw).replace(/<[^>]+>/, "").trim() || email; const subject = stringValue(body.subject) || "Antwort auf deine Anfrage"; const messageId = stringValue(body.messageId); const text = stringValue(body.text) || stringValue(body.body) || stripHtml(stringValue(body.html)); if (!email || !messageId || !text) return NextResponse.json({ error: "Absender, messageId und Inhalt sind erforderlich." }, { status: 400 }); try { const payload = await getPayload({ config }); if (messageId) { const existing = await payload.find({ collection: "conversation-messages", where: { externalMessageId: { equals: messageId } }, limit: 1, overrideAccess: true }); if (existing.docs[0]) return NextResponse.json({ ok: true, duplicate: true }); } const threadKey = findThreadKey(subject); let conversation = threadKey ? (await payload.find({ collection: "conversations", where: { threadKey: { equals: threadKey } }, limit: 1, overrideAccess: true })).docs[0] : undefined; if (!conversation) { conversation = (await payload.find({ collection: "conversations", where: { email: { equals: email }, status: { not_equals: "closed" } }, sort: "-lastMessageAt", limit: 1, overrideAccess: true })).docs[0]; } if (!conversation) return NextResponse.json({ error: "Keine passende Konversation gefunden." }, { status: 404 }); await payload.create({ collection: "conversation-messages", overrideAccess: true, data: { conversation: conversation.id, senderType: "visitor", senderName: name, senderEmail: email, body: text.slice(0, 10000), externalMessageId: messageId, }, }); await payload.update({ collection: "conversations", id: conversation.id, overrideAccess: true, data: { status: "open", lastMessageAt: new Date().toISOString() } }); return NextResponse.json({ ok: true }); } catch (error) { console.error("inbound email failed", error); return NextResponse.json({ error: "E-Mail konnte nicht übernommen werden." }, { status: 500 }); } }