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>
This commit is contained in:
108
app/api/generate-email/route.ts
Normal file
108
app/api/generate-email/route.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user