Files
lead-scraper/app/api/generate-email/route.ts
Timo Uttenweiler 54e0d22f9c mein-solar: full feature set
- Schema: companyType, topics, salesScore, salesReason, offerPackage, approved, approvedAt, SearchHistory table
- /api/search-history: GET (by mode) + POST (save query)
- /api/ai-search: stadtwerke/industrie/custom prompts with history dedup
- /api/enrich-leads: website scraping + GPT-4o-mini enrichment (fire-and-forget after each job)
- /api/generate-email: personalized outreach via GPT-4o
- Suche page: 3 mode tabs (Stadtwerke/Industrie/Freie Suche), Alle-Bundesländer queue button, AiSearchModal gets searchMode + history
- Leadspeicher: Bewertung dots column, Paket badge column, Freigeben toggle button, email generator in SidePanel, approved-only export option
- Leads API: approvedOnly + companyType filters, new fields in PATCH

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 21:06:07 +02:00

109 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
const EMAIL_PROMPT_TEMPLATE = `Du schreibst eine professionelle Erstansprache-E-Mail für mein-solar.com an einen potenziellen Kunden.
mein-solar.com ist ein Full-Service EPC-Anbieter für Photovoltaik (5 kW bis 10+ MW), Batteriespeicher und Ladeinfrastruktur.
Firmendaten des Empfängers:
- Unternehmen: [companyName]
- Ansprechpartner: [contactName], [contactTitle]
- Unternehmenstyp: [companyType]
- Themen des Unternehmens: [topics]
- Empfohlenes Paket: [offerPackage]
Schreibe eine E-Mail auf Deutsch:
- Betreff: prägnant, bezieht sich auf konkretes Thema des Unternehmens
- Anrede: personalisiert wenn Name bekannt
- 2-3 kurze Absätze: Bezug auf das Unternehmen → Mehrwert von mein-solar → konkreter nächster Schritt (Erstgespräch)
- Ton: professionell, direkt, kein Marketing-Sprech
- Keine Buzzwords wie "innovativ" oder "nachhaltig"
- Signatur: [Vorname] von mein-solar.com
Antworte mit JSON: { "subject": "...", "body": "..." }`;
export async function POST(req: NextRequest) {
try {
const { leadId } = await req.json() as { leadId: string };
if (!leadId) {
return NextResponse.json({ error: "leadId fehlt" }, { status: 400 });
}
const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) {
return NextResponse.json({ error: "OpenRouter API Key nicht konfiguriert" }, { status: 500 });
}
const lead = await prisma.lead.findUnique({ where: { id: leadId } });
if (!lead) {
return NextResponse.json({ error: "Lead nicht gefunden" }, { status: 404 });
}
if (!lead.email) {
return NextResponse.json({ error: "Lead hat keine E-Mail-Adresse" }, { status: 400 });
}
let topicsText = "";
if (lead.topics) {
try {
const topicsArr = JSON.parse(lead.topics) as string[];
topicsText = topicsArr.join(", ");
} catch {
topicsText = lead.topics;
}
}
const prompt = EMAIL_PROMPT_TEMPLATE
.replace("[companyName]", lead.companyName || "Unbekannt")
.replace("[contactName]", lead.contactName || "")
.replace("[contactTitle]", lead.contactTitle || "")
.replace("[companyType]", lead.companyType || "")
.replace("[topics]", topicsText || "")
.replace("[offerPackage]", lead.offerPackage || "");
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://mein-solar.com",
"X-Title": "MeinSolar Leads",
},
body: JSON.stringify({
model: "openai/gpt-4o",
temperature: 0.5,
max_tokens: 600,
messages: [
{ role: "user", content: prompt },
],
}),
});
if (!res.ok) {
const err = await res.text();
console.error("[generate-email] OpenRouter error:", err);
return NextResponse.json({ error: "KI-Anfrage fehlgeschlagen" }, { status: 500 });
}
const data = await res.json() as { choices: Array<{ message: { content: string } }> };
const raw = data.choices[0]?.message?.content?.trim() ?? "";
let email: { subject: string; body: string };
try {
email = JSON.parse(raw) as { subject: string; body: string };
} catch {
const match = raw.match(/\{[\s\S]*\}/);
if (!match) {
return NextResponse.json({ error: "KI-Antwort konnte nicht geparst werden" }, { status: 500 });
}
email = JSON.parse(match[0]) as { subject: string; body: string };
}
return NextResponse.json({ subject: email.subject, body: email.body });
} catch (err) {
console.error("POST /api/generate-email error:", err);
return NextResponse.json({ error: "E-Mail-Generierung fehlgeschlagen" }, { status: 500 });
}
}