# Personalisierter Email Outreach Agent -- Vollstaendiges Konzept
**Datum:** 2026-04-24
**Autor:** AG (Agent Solutions)
**Auftraggeber:** Karlo
**Ziel:** System das aus unvollstaendigen Excel-Kontaktdaten personalisierte, branchenspezifische Kaltakquise-Emails generiert -- fuer zwei getrennte ICPs (LP und AG).
---
## 1. Architektur-Uebersicht
### Was existiert bereits
| Modul | Datei | Funktion | Aenderung noetig? |
|-------|-------|----------|-------------------|
| Cold Outreach DB | `cold_outreach_db.py` | Kontakte, 3-Step-Sequenzen, Statistik | Ja -- Schema-Erweiterung |
| Email Classifier | `email_classifier.py` | Claude-Klassifikation eingehender Emails | Ja -- Reply-Detection |
| SendGrid Client | `sendgrid_integration.py` | Email-Versand | Nein -- funktioniert |
| Gmail Client | `gmail_integration.py` | Email-Empfang (OAuth2) | Nein |
| Database | `database.py` | email_log, email_queue, email_templates | Nein |
| Agent Orchestrator | `agent.py` | Continuous loop (Inbound) | Ja -- Outbound-Loop integrieren |
| Response Templates | `response_templates.py` | Statische Templates (Inbound) | Nein (nicht fuer Outreach) |
| YouTube Research DB | `../YouTube_Research/knowledge.db` | Videos, Transkripte, Summaries, Skills | Nein -- nur lesen |
### Was neu gebaut werden muss
| Modul | Datei (neu) | Funktion |
|-------|-------------|----------|
| Excel Importer | `excel_importer.py` | Excel/CSV einlesen, validieren, in cold_contacts schreiben |
| Contact Enricher | `contact_enricher.py` | Fehlende Daten anreichern (Website-Scraping, Claude-Analyse) |
| ICP Classifier | `icp_classifier.py` | Bestimmt ob Kontakt zu LP, AG oder beiden passt |
| Outreach Generator | `outreach_generator.py` | Claude-basierte personalisierte Email-Generierung |
| Knowledge Retriever | `knowledge_retriever.py` | RAG-Zugriff auf YouTube Research knowledge.db |
| Outreach Orchestrator | `outreach_orchestrator.py` | Steuert den gesamten Outbound-Workflow |
| Review Interface | `review_cli.py` | CLI fuer manuelle Pruefung vor Versand |
### Architektur-Diagramm
```
Excel/CSV
|
v
[excel_importer.py] --> cold_contacts (roh, unvollstaendig)
|
v
[contact_enricher.py] --> cold_contacts (angereichert)
| |
| +-- Website-Scraping (requests + BeautifulSoup)
| +-- Claude-Analyse (Branche/Schmerz aus Website-Text ableiten)
|
v
[icp_classifier.py] --> cold_contacts.icp = AG / LP / beide
|
v
[outreach_generator.py]
| |
| +-- [knowledge_retriever.py] --> YouTube knowledge.db (RAG)
| +-- Claude API (Personalisierung)
| +-- ICP-spezifische Prompt-Templates
|
v
cold_sequences (subject + body befuellt)
|
v
[review_cli.py] --> Mensch prueft, genehmigt oder editiert
|
v
[outreach_orchestrator.py] --> SendGrid-Versand
|
v
[agent.py] --> Reply-Detection (Inbound-Loop erkennt Antworten)
```
---
## 2. Excel-Import und Enrichment Pipeline
### 2.1 Erwartetes Excel-Format
Mario liefert eine Excel-Datei. Der Importer muss flexibel sein, weil Spaltenbezeichnungen variieren koennen.
**Mindest-Spalten (eine davon muss vorhanden sein):**
| Spalte | Pflicht | Beispiel |
|--------|---------|----------|
| Name / Vorname / first_name | Ja | "Maria" |
| Email / E-Mail / email | Ja | "maria@hotel-sol.es" |
**Optionale Spalten (werden gemappt wenn vorhanden):**
| Spalte | Mapping auf | Beispiel |
|--------|-------------|----------|
| Nachname / last_name | last_name | "Garcia" |
| Firma / Company / Unternehmen | company | "Hotel Sol Mallorca" |
| Position / Rolle / Title | role | "Geschaeftsfuehrerin" |
| Branche / Industry | industry | "Hotellerie" |
| Website / URL / Web | website | "www.hotel-sol.es" |
| Telefon / Phone | phone | "+34 971 123456" |
| Notizen / Notes | notes | "Kontakt von Messe" |
### 2.2 Spalten-Mapping-Logik
```python
# excel_importer.py -- Kernlogik
COLUMN_ALIASES = {
'first_name': ['name', 'vorname', 'first_name', 'firstname', 'nombre'],
'last_name': ['nachname', 'last_name', 'lastname', 'apellido'],
'email': ['email', 'e-mail', 'mail', 'correo'],
'company': ['firma', 'company', 'unternehmen', 'empresa'],
'role': ['position', 'rolle', 'title', 'job_title', 'cargo'],
'industry': ['branche', 'industry', 'sektor', 'sector'],
'website': ['website', 'url', 'web', 'homepage', 'webseite'],
'phone': ['telefon', 'phone', 'tel', 'telefono'],
'notes': ['notizen', 'notes', 'bemerkung', 'comentarios'],
}
```
Der Importer normalisiert alle Spaltennamen (lowercase, strip) und mappt sie ueber die Alias-Tabelle. Nicht erkannte Spalten werden in `notes` konkateniert.
### 2.3 Import-Ablauf
1. Excel laden (`openpyxl` fuer .xlsx, `csv` fuer .csv)
2. Spaltennamen normalisieren und mappen
3. Zeilen validieren: Email-Format pruefen, Duplikate gegen DB checken
4. Gueltige Kontakte in `cold_contacts` mit `status = 'raw'` schreiben
5. Report ausgeben: X importiert, Y uebersprungen (Duplikate), Z ungueltig (keine Email)
### 2.4 Enrichment Pipeline
Das Kernproblem: Marios Excel hat oft nur Name + Email + vielleicht Firma. Ohne Branche und Schmerzpunkt ist keine Personalisierung moeglich.
**Enrichment-Strategie (semi-automatisch):**
```
Kontakt mit status='raw'
|
v
[Schritt 1] Website vorhanden?
|-- Ja --> Website scrapen, Meta-Tags + Haupttext extrahieren
|-- Nein --> Domain aus Email ableiten (maria@hotel-sol.es --> hotel-sol.es)
| --> Versuche Website zu scrapen
| --> Kein Ergebnis? --> status='needs_manual'
|
v
[Schritt 2] Claude analysiert Website-Text
|
Prompt: "Analysiere diese Website. Bestimme:
| 1. Branche (aus vordefinierter Liste)
| 2. Haupttaetigkeit in 1 Satz
| 3. Wahrscheinlicher Schmerzpunkt (Lueftung/Schimmel ODER Automatisierung)
| 4. Unternehmensgroesse (Einschaetzung)"
|
v
[Schritt 3] Ergebnis in cold_contacts schreiben
|-- industry, pain_point, company_description (neues Feld)
|-- status = 'enriched'
|
v
[Schritt 4] Manuelle Review fuer 'needs_manual' Kontakte
|-- CLI zeigt: Name, Email, Firma
|-- Mensch gibt Branche + Schmerzpunkt ein
|-- status = 'enriched'
```
### 2.5 Mindest-Datenprofil fuer Personalisierung
Ein Kontakt darf erst in die Email-Generierung, wenn diese Felder befuellt sind:
| Feld | Warum noetig | Quelle |
|------|-------------|--------|
| first_name | Anrede | Excel |
| email | Versandziel | Excel |
| company | Bezugnahme in Email | Excel oder Enrichment |
| industry | Bestimmt ICP + Ton | Enrichment (Claude) |
| pain_point | Kern der Personalisierung | Enrichment (Claude) |
| icp | LP/AG/beide -- steuert welches Produkt | ICP Classifier |
Kontakte ohne diese Felder bleiben auf `status='needs_manual'` und werden NICHT automatisch angeschrieben.
---
## 3. Personalisierungs-Engine (Kernstueck)
### 3.1 Grundprinzip
Keine generischen Emails. Jede Email referenziert:
- Die konkrete Branche des Empfaengers
- Ein reales Problem das diese Branche hat
- Eine spezifische Loesung die wir anbieten
- Optional: Ein Wissens-Nugget aus der YouTube Research DB
### 3.2 Claude-Prompt-Architektur
Der Outreach Generator arbeitet mit einem geschichteten Prompt-System:
```
[System-Prompt] -- Rolle + Stil + Regeln
+
[ICP-Prompt] -- LP-spezifisch ODER AG-spezifisch
+
[Kontakt-Kontext] -- Branche, Schmerz, Firma
+
[Knowledge-Snippet] -- Relevantes Wissen aus YouTube DB (optional)
+
[Step-Anweisung] -- Email 1, 2 oder 3
=
Personalisierte Email
```
### 3.3 System-Prompt (fuer alle Emails)
```python
SYSTEM_PROMPT = """Du bist ein Email-Copywriter fuer personalisierte B2B-Kaltakquise.
REGELN:
- Schreibe auf Deutsch (oder Spanisch wenn der Kontakt spanischsprachig ist)
- Maximal 150 Woerter pro Email
- Kein Marketing-Sprech, kein "Wir sind die Besten"
- Schreibe wie ein Mensch, nicht wie eine Maschine
- Erster Satz muss sich auf die konkrete Situation des Empfaengers beziehen
- Keine generischen Floskeln wie "Ich hoffe diese Email erreicht Sie wohlauf"
- Call-to-Action: Immer eine konkrete, niedrigschwellige Frage
- Kein Anhang erwaehnen, keine Links in Email 1
- Signatur: Nur Vorname + Firma + Telefonnummer
OUTPUT: Ausschliesslich JSON:
{
"subject": "...",
"body": "...",
"personalization_score": 0.0-1.0,
"personalization_elements": ["element1", "element2"]
}
personalization_score < 0.6 = Email wird NICHT gesendet (zu generisch).
personalization_elements = Liste der konkreten Bezugspunkte im Text.
"""
```
### 3.4 ICP-spezifische Prompts
**LP-Prompt (Lueftungsprofi -- Mallorca AirServices):**
```python
LP_CONTEXT = """
ABSENDER: Karlo, Mallorca AirServices (LueftungsProfi)
PRODUKT: Dezentrale Lueftungssysteme (getAir), CO2/Feuchte-Messung, Schimmelpraevention
REGION: Mallorca (Balearen)
ZIELGRUPPEN: Immobilienverwalter, Hoteliers, Buerogebaeude-Eigentuemer, Wohnungsbaugesellschaften
KERNPROBLEM DAS WIR LOESEN: Schlechte Luftqualitaet, Schimmel durch Feuchtigkeit, hohe Energiekosten durch ineffiziente Lueftung
ALLEINSTELLUNGSMERKMAL: Lokaler Spezialist auf Mallorca, deutschsprachig, getAir-Partner
TON: Professionell aber persoenlich. Regional verankert. Nicht verkaeuferhaft. Wie ein Nachbar der zufaellig Experte ist.
SPRACHE: Deutsch wenn deutschsprachiger Kontakt, Spanisch wenn spanischsprachig.
BRANCHEN-SPEZIFISCHE SCHMERZEN:
- Hotels: Gaestebeschwerden ueber muffige Zimmer, Schimmel im Bad, hohe Klimaanlagen-Kosten
- Immobilienverwalter: Schimmelreklamationen der Mieter, Sanierungskosten, Versicherungsfaelle
- Buerogebaeude: Mitarbeiter-Produktivitaet sinkt bei schlechter Luft, Krankenstand
- Wohnungsbau: Baunormen (CTE/RITE in Spanien), Lueftungskonzepte fuer Neubau
"""
```
**AG-Prompt (Agent Solutions + Portier):**
```python
AG_CONTEXT = """
ABSENDER: Karlo, Agent Solutions
PRODUKTE:
1. Email Agent -- KI-basierte Email-Automatisierung (Klassifikation, Auto-Reply, Routing)
2. Telefon Agent -- KI-Telefonassistent (IVR, Spracherkennung, automatische Anrufbearbeitung)
3. The Donna -- KI-Assistent (Electron Desktop App)
4. Portier -- KI-gestuetzter Concierge/Portier-Service (fuer Hotels/Immobilien) [Mario's Projekt]
5. KI-Kurse -- Online-Kurse zu KI-Themen
ZIELGRUPPEN: KMUs, Mittelstaendler, kleine Dienstleister
KERNPROBLEM: Zu viel manuelle Arbeit bei Emails, Telefonaten, Kundenservice. Zu teuer fuer grosse Enterprise-Loesungen.
ALLEINSTELLUNGSMERKMAL: DIY-Loesungen, transparent, keine Vendor-Lock-In, 10x guenstiger als Fonica etc.
TON: Technisch kompetent aber verstaendlich. Kein Buzzword-Bingo. Konkrete Zahlen (Kosten, Zeitersparnis).
SPRACHE: Deutsch.
BRANCHEN-SPEZIFISCHE SCHMERZEN:
- Handwerker/Dienstleister: Verpassen Anrufe waehrend sie arbeiten, verlieren Auftraege
- Immobilienmakler: Ertrinken in Email-Anfragen, antworten zu langsam
- Arztpraxen/Therapeuten: Telefon klingelt permanent, Rezeption ueberfordert
- Hotels: Gaesteanfragen 24/7, Nachtschicht teuer, mehrsprachig noetig --> Portier
- Online-Shops: Kundenservice-Emails fressen Zeit
"""
```
### 3.5 Step-spezifische Anweisungen
**Step 1 (Tag 0) -- Erster Kontakt:**
```
Ziel: Aufmerksamkeit wecken durch relevante Beobachtung.
Struktur:
- Satz 1: Konkreter Bezug zur Firma/Branche des Empfaengers
- Satz 2-3: Problem benennen das der Empfaenger wahrscheinlich hat
- Satz 4: Andeuten dass es eine Loesung gibt (nicht verkaufen!)
- Satz 5: Einfache Frage ("Ist das ein Thema bei Ihnen?")
Laenge: 80-120 Woerter
Kein Link, kein Anhang.
```
**Step 2 (Tag 4) -- Follow-Up mit Mehrwert:**
```
Ziel: Wert liefern, nicht nerven.
Struktur:
- Satz 1: Kurzer Bezug auf Email 1 (NICHT "haben Sie meine Email gelesen")
- Satz 2-4: Konkretes Wissens-Nugget oder Statistik aus YouTube Research DB
- Satz 5: "Falls Sie X Minuten haben, zeige ich Ihnen gerne wie Y funktioniert"
Laenge: 100-150 Woerter
Optional: Ein Link zur Website oder einem relevanten Inhalt.
```
**Step 3 (Tag 8) -- Letzter Versuch:**
```
Ziel: Tuer offenlassen, nicht bedraengen.
Struktur:
- Satz 1: "Ich moechte nicht nerven"
- Satz 2: Nochmal den Kern-Nutzen in einem Satz
- Satz 3: "Falls es gerade nicht passt -- kein Problem. Ich bin da wenn es soweit ist."
- Optional: Referenz auf einen anderen Kunden in gleicher Branche (Social Proof)
Laenge: 60-80 Woerter
Kurz und respektvoll. Kein Druck.
```
### 3.6 YouTube Research DB als Knowledge Base (RAG-Prinzip)
Die `knowledge.db` enthaelt Videos, Transkripte und Summaries ueber KI-Automatisierung, Claude Code, etc. Diese werden als Wissensquelle fuer Step-2-Emails genutzt.
**Ablauf im `knowledge_retriever.py`:**
```python
# 1. Suchbegriffe ableiten aus Kontakt-Kontext
# Branche: "Hotel" --> Suchbegriffe: "hotel automation", "customer service AI"
# Branche: "Handwerker" --> Suchbegriffe: "small business AI", "phone automation"
# 2. FTS5-Suche in knowledge.db (bestehende search.py nutzen)
# SELECT title, summary FROM videos
# WHERE videos_fts MATCH ? AND domain_id = ?
# ORDER BY bm25(videos_fts) LIMIT 3
# 3. Relevantestes Summary als Snippet extrahieren (max 200 Woerter)
# 4. Snippet in den Claude-Prompt einbetten:
# "Nutze folgendes Wissen als Basis fuer ein konkretes Beispiel
# oder eine Statistik in der Email: {snippet}"
```
**Wichtig:** Das RAG-Snippet wird NUR fuer AG-Emails verwendet (KI-Automatisierung). Fuer LP-Emails (Lueftung) gibt es keine YouTube-Knowledge -- dort kommt der Mehrwert aus hartkodierten Branchen-Fakten (Schimmelstatistiken, Energiekosten-Vergleiche, spanische Baunormen).
### 3.7 LP-Fakten-Datenbank (statt RAG)
Fuer LP-Emails wird ein statisches Fakten-Dict verwendet:
```python
LP_FACTS = {
"hotel": [
"Laut WHO sinkt die Gaestezufriedenheit um 23% bei schlechter Raumluft.",
"Dezentrale Lueftung spart gegenueber zentralen Anlagen 30-40% Installationskosten.",
"Auf Mallorca liegt die Luftfeuchtigkeit 8 Monate im Jahr ueber 65% -- Schimmelgrenze.",
],
"immobilienverwaltung": [
"Schimmelbeseitigung kostet im Schnitt 3.000-8.000 EUR pro Wohnung.",
"Praeventive Lueftung reduziert Schimmelreklamationen um bis zu 90%.",
"CO2-Sensoren erkennen Belegung und regeln automatisch -- spart Energie bei Leerstand.",
],
"buero": [
"Studien zeigen: Bei CO2 ueber 1000ppm sinkt die kognitive Leistung um 15%.",
"Krankenstand reduziert sich um 35% bei kontrollieter Raumluft.",
"Dezentrale Geraete koennen raumweise nachgeruestet werden -- kein Umbau noetig.",
],
"wohnungsbau": [
"Das spanische CTE verlangt seit 2020 mechanische Lueftung in Neubauten.",
"getAir-Geraete erfuellen die RITE-Anforderungen fuer Wohnraumlueftung.",
"Waermerueckgewinnung bis 93% -- reduziert Klimatisierungskosten erheblich.",
],
}
```
---
## 4. Kundendaten-Klassifikation (ICP Classifier)
### 4.1 Regelbasierte Vor-Klassifikation
Bevor Claude gefragt wird, filtert ein regelbasiertes System offensichtliche Zuordnungen:
```python
LP_INDUSTRIES = {
'hotel', 'hotellerie', 'hospitality', 'hostal', 'hostel',
'immobilien', 'real_estate', 'inmobiliaria', 'hausverwaltung',
'property_management', 'bauunternehmen', 'construccion',
'architektur', 'arquitectura', 'facility_management',
}
AG_INDUSTRIES = {
'it', 'software', 'saas', 'ecommerce', 'online_shop',
'beratung', 'consulting', 'marketing', 'agentur', 'agency',
'rechtsanwalt', 'steuerberater', 'arztpraxis', 'therapeut',
'handwerk', 'dienstleistung', 'versicherung',
}
BOTH_INDUSTRIES = {
'hotel', # LP: Lueftung + AG: Portier/Telefon-Agent
'immobilien', # LP: Lueftung + AG: Email-Agent
}
```
### 4.2 Claude-basierte Fein-Klassifikation
Wenn die regelbasierte Zuordnung nicht eindeutig ist (Branche nicht in den Listen), fragt Claude:
```python
ICP_CLASSIFICATION_PROMPT = """
Analysiere diesen Geschaeftskontakt und bestimme welches Produkt am besten passt.
KONTAKT:
- Firma: {company}
- Branche: {industry}
- Taetigkeit: {company_description}
- Schmerzpunkt: {pain_point}
PRODUKT LP (Mallorca AirServices):
Dezentrale Lueftung, Schimmelpraevention, CO2-Messung.
Relevant fuer: Gebaeude-Eigentuemer, Verwalter, Hotels, Bauunternehmen auf Mallorca.
PRODUKT AG (Agent Solutions):
KI-Email-Agent, KI-Telefon-Agent, Portier (Hotel-Concierge-KI).
Relevant fuer: Jedes KMU mit hohem Email/Telefon-Aufkommen.
Antwort als JSON:
{
"icp": "LP" | "AG" | "beide",
"reasoning": "...",
"recommended_product": "...",
"confidence": 0.0-1.0
}
"""
```
### 4.3 Sonderfall "beide"
Wenn ein Hotel auf Mallorca sowohl Lueftungsprobleme haben koennte ALS AUCH den Portier/Telefon-Agent braucht, wird der Kontakt als `icp='beide'` markiert. Die Sequenz laeuft dann nur fuer EIN Produkt (das mit hoeherem Confidence-Score). Das zweite Produkt wird erst angeboten, wenn der Kontakt auf das erste reagiert hat.
---
## 5. Vollstaendiger Workflow (Schritt fuer Schritt)
### Phase 1: Import (einmalig pro Excel-Datei)
```
1. Mario liefert Excel an Karlo (per WhatsApp/Email)
2. Karlo legt Datei in K:\projekte-AG\email_agent\import\
3. Karlo fuehrt aus:
python excel_importer.py --file import/marios_leads.xlsx
4. Output:
"47 Kontakte importiert, 3 Duplikate uebersprungen, 2 ohne Email ignoriert"
"47 Kontakte mit status='raw' -- bereit fuer Enrichment"
```
### Phase 2: Enrichment (semi-automatisch)
```
5. Karlo fuehrt aus:
python contact_enricher.py --batch 50
6. System:
- Leitet Website aus Email-Domain ab
- Scraped Website (Title, Meta-Description, Haupttext)
- Claude analysiert und fuellt industry, pain_point, company_description
7. Output:
"38 Kontakte automatisch angereichert (status='enriched')"
"9 Kontakte brauchen manuelle Eingabe (status='needs_manual')"
8. Fuer manuelle Kontakte:
python contact_enricher.py --manual
> Kontakt: Hans Mueller (mueller@firma.de) -- Firma: Mueller GmbH
> Branche? [Eingabe]: Sanitaer
> Hauptproblem? [Eingabe]: Verpasst Anrufe auf Baustelle
> [Gespeichert]
```
### Phase 3: ICP-Klassifikation (automatisch)
```
9. Karlo fuehrt aus:
python icp_classifier.py --classify-all
10. System:
- Regelbasiert wo moeglich
- Claude fuer unklare Faelle
11. Output:
"23 Kontakte --> LP, 19 Kontakte --> AG, 5 Kontakte --> beide"
```
### Phase 4: Email-Generierung (automatisch + Review)
```
12. Karlo fuehrt aus:
python outreach_generator.py --generate-step 1
13. System:
- Laedt alle Kontakte mit status='enriched' und sequence_step 1 pending
- Generiert personalisierte Email pro Kontakt via Claude
- Prueft personalization_score >= 0.6
- Speichert subject + body in cold_sequences
14. Output:
"47 Emails generiert. 44 ueber Schwellenwert (score >= 0.6). 3 zu generisch -- uebersprungen."
```
### Phase 5: Review (manuell -- KRITISCH)
```
15. Karlo fuehrt aus:
python review_cli.py
16. CLI zeigt Email fuer Email:
================================================================
AN: Maria Garcia (maria@hotel-sol.es) -- Hotel Sol Mallorca
ICP: LP | Branche: Hotel | Score: 0.82
----------------------------------------------------------------
Betreff: Luftqualitaet in Ihren Zimmern
----------------------------------------------------------------
Hallo Frau Garcia,
als Hotelbetreiberin auf Mallorca kennen Sie das Problem:
Gaeste beschweren sich ueber stickige Zimmer, besonders
in den feuchten Wintermonaten. [...]
Ist das ein Thema bei Ihnen im Hotel Sol?
Beste Gruesse
Karlo
Mallorca AirServices
+34 644 931 559
----------------------------------------------------------------
[G]enehmigen [E]ditieren [S]kippen [R]egenerieren [Q]uit
>
17. Karlo geht jede Email durch:
- G: Email wird auf status='approved' gesetzt
- E: Editor oeffnet sich, Karlo passt Text an
- S: Email wird uebersprungen (status='skipped')
- R: Neue Generierung mit anderem Ansatz
```
### Phase 6: Versand (automatisch)
```
18. Karlo fuehrt aus:
python outreach_orchestrator.py --send-approved
19. System:
- Sendet alle approved Emails via SendGrid
- Markiert als 'sent'
- Loggt Timestamp
20. Output:
"44 Emails gesendet. 0 fehlgeschlagen."
"Naechste Step-2-Emails faellig am: 2026-04-28"
```
### Phase 7: Reply-Detection (continuous)
```
21. Der bestehende agent.py (Inbound-Loop) wird erweitert:
- Pruefte eingehende Emails gegen cold_contacts (Email-Match)
- Bei Match: cold_outreach_db.mark_replied(email)
- Alle pending Sequenzen fuer diesen Kontakt werden uebersprungen
- Karlo bekommt Notification: "Maria Garcia hat geantwortet!"
```
### Phase 8: Step 2 und 3 (automatisch getriggert)
```
22. Am Tag 4 (und Tag 8):
python outreach_orchestrator.py --generate-and-review
23. Nur fuer Kontakte die NICHT geantwortet haben
24. Gleicher Review-Prozess wie Phase 5
```
---
## 6. Fehlende Module (was zu coden ist)
### 6.1 `excel_importer.py` -- NEU
**Zweck:** Excel/CSV in cold_contacts importieren mit flexiblem Spalten-Mapping.
**Kernfunktionen:**
- `import_excel(file_path: str) -> ImportReport` -- Hauptfunktion
- `_detect_columns(headers: list) -> dict` -- Spalten-Mapping via Alias-Tabelle
- `_validate_row(row: dict) -> tuple[bool, str]` -- Email-Validierung
- `_check_duplicate(email: str) -> bool` -- Gegen DB pruefen
**Dependencies:** `openpyxl`, `csv`, bestehende `cold_outreach_db.py`
**Aufwand:** ~150 Zeilen, 2-3 Stunden
### 6.2 `contact_enricher.py` -- NEU
**Zweck:** Fehlende Kontaktdaten automatisch anreichern.
**Kernfunktionen:**
- `enrich_batch(limit: int) -> EnrichmentReport` -- Batch-Enrichment
- `_scrape_website(url: str) -> str` -- Website-Text extrahieren
- `_derive_website_from_email(email: str) -> str` -- Domain ableiten
- `_analyze_with_claude(website_text: str) -> dict` -- Branche/Schmerz bestimmen
- `enrich_manual()` -- Interaktiver CLI-Modus
**Dependencies:** `requests`, `beautifulsoup4`, `anthropic`, bestehende `cold_outreach_db.py`
**Aufwand:** ~250 Zeilen, 4-5 Stunden
### 6.3 `icp_classifier.py` -- NEU
**Zweck:** Kontakte dem richtigen ICP (LP/AG/beide) zuordnen.
**Kernfunktionen:**
- `classify_all() -> ClassificationReport` -- Alle unklassifizierten Kontakte
- `_rule_based_classify(contact: dict) -> str | None` -- Schnelle Zuordnung
- `_claude_classify(contact: dict) -> dict` -- Fein-Klassifikation
**Dependencies:** `anthropic`, bestehende `cold_outreach_db.py`
**Aufwand:** ~120 Zeilen, 2 Stunden
### 6.4 `outreach_generator.py` -- NEU
**Zweck:** Personalisierte Emails via Claude generieren.
**Kernfunktionen:**
- `generate_step(step: int) -> GenerationReport` -- Emails fuer einen Step generieren
- `_build_prompt(contact: dict, step: int, knowledge: str) -> str` -- Prompt bauen
- `_validate_output(result: dict) -> bool` -- Score >= 0.6, JSON-Schema pruefen
- `regenerate(seq_id: int) -> dict` -- Einzelne Email neu generieren
**Dependencies:** `anthropic`, `cold_outreach_db.py`, `knowledge_retriever.py`
**Aufwand:** ~300 Zeilen, 5-6 Stunden
### 6.5 `knowledge_retriever.py` -- NEU
**Zweck:** RAG-Zugriff auf YouTube Research knowledge.db.
**Kernfunktionen:**
- `get_relevant_knowledge(industry: str, pain_point: str) -> str` -- Bestes Snippet
- `_build_search_query(industry: str) -> str` -- Suchbegriffe ableiten
- `_extract_snippet(summary: str, max_words: int) -> str` -- Relevanten Ausschnitt
**Dependencies:** `sqlite3`, Zugriff auf `K:/projekte-AG/YouTube_Research/knowledge.db`
**Aufwand:** ~100 Zeilen, 2 Stunden
### 6.6 `outreach_orchestrator.py` -- NEU
**Zweck:** Steuert den gesamten Outbound-Workflow.
**Kernfunktionen:**
- `send_approved() -> SendReport` -- Alle genehmigten Emails versenden
- `check_due_emails() -> list` -- Faellige Step-2/3 Emails finden
- `generate_and_review(step: int)` -- Generierung + Review in einem Schritt
- `get_campaign_stats() -> dict` -- Ueberblick ueber alle Kampagnen
**Dependencies:** `sendgrid_integration.py`, `cold_outreach_db.py`, `outreach_generator.py`
**Aufwand:** ~200 Zeilen, 3-4 Stunden
### 6.7 `review_cli.py` -- NEU
**Zweck:** CLI fuer manuelle Email-Pruefung vor Versand.
**Kernfunktionen:**
- `review_pending()` -- Interaktive Review-Session
- `_display_email(seq: dict)` -- Formatierte Anzeige
- `_edit_email(seq: dict) -> dict` -- Editor oeffnen
- `_approve(seq_id: int)` -- Status auf approved setzen
**Dependencies:** `cold_outreach_db.py`
**Aufwand:** ~150 Zeilen, 2-3 Stunden
### 6.8 Aenderungen an bestehenden Modulen
**`agent.py` -- Erweiterung:**
- Reply-Detection in den Inbound-Loop einbauen
- Wenn eingehende Email von einer Adresse in cold_contacts kommt: `mark_replied()` aufrufen
- ~30 Zeilen Aenderung
**`cold_outreach_db.py` -- Schema-Erweiterung:**
- Neue Felder (siehe Abschnitt 7)
- Neue Status-Werte
- ~50 Zeilen Aenderung
**Gesamtaufwand: ~1.350 Zeilen neuer Code, ~80 Zeilen Aenderungen. Geschaetzt 20-25 Stunden Entwicklung.**
---
## 7. Datenbankschema-Erweiterungen
### 7.1 Erweiterung der `cold_contacts` Tabelle
Neue Felder die zur bestehenden Tabelle hinzugefuegt werden muessen:
```sql
ALTER TABLE cold_contacts ADD COLUMN website TEXT;
ALTER TABLE cold_contacts ADD COLUMN phone TEXT;
ALTER TABLE cold_contacts ADD COLUMN company_description TEXT;
-- Kurzbeschreibung der Firma (1-2 Saetze, von Claude generiert)
ALTER TABLE cold_contacts ADD COLUMN enrichment_source TEXT;
-- 'auto_website' | 'auto_email_domain' | 'manual' | NULL
ALTER TABLE cold_contacts ADD COLUMN enrichment_date DATETIME;
ALTER TABLE cold_contacts ADD COLUMN icp_confidence REAL DEFAULT 0.0;
-- Wie sicher ist die ICP-Zuordnung (0.0-1.0)
ALTER TABLE cold_contacts ADD COLUMN icp_recommended_product TEXT;
-- Konkretes Produkt: 'lueftung' | 'email_agent' | 'telefon_agent' | 'portier' | 'ki_kurse'
ALTER TABLE cold_contacts ADD COLUMN language TEXT DEFAULT 'de';
-- 'de' | 'es' | 'en'
```
### 7.2 Erweiterung der Status-Werte fuer `cold_contacts`
Bestehend: `active`, `paused`, `unsubscribed`, `replied`, `converted`
Neu:
| Status | Bedeutung |
|--------|-----------|
| `raw` | Frisch importiert, noch nicht angereichert |
| `enriched` | Angereichert, bereit fuer Klassifikation und Email-Generierung |
| `needs_manual` | Enrichment fehlgeschlagen, braucht manuelle Eingabe |
| `active` | (bestehend) Sequenz laeuft |
| `paused` | (bestehend) Manuell pausiert |
| `replied` | (bestehend) Hat geantwortet |
| `converted` | (bestehend) Wurde Kunde |
| `bounced` | Email-Adresse ungueltig (Bounce von SendGrid) |
| `unsubscribed` | (bestehend) Hat sich abgemeldet |
### 7.3 Erweiterung der `cold_sequences` Tabelle
```sql
ALTER TABLE cold_sequences ADD COLUMN personalization_score REAL;
-- Score von Claude (0.0-1.0)
ALTER TABLE cold_sequences ADD COLUMN personalization_elements TEXT;
-- JSON-Array der Bezugspunkte
ALTER TABLE cold_sequences ADD COLUMN review_status TEXT DEFAULT 'pending';
-- 'pending' | 'approved' | 'rejected' | 'edited'
ALTER TABLE cold_sequences ADD COLUMN reviewed_at DATETIME;
ALTER TABLE cold_sequences ADD COLUMN reviewed_by TEXT DEFAULT 'karlo';
ALTER TABLE cold_sequences ADD COLUMN knowledge_snippet TEXT;
-- Das RAG-Snippet das verwendet wurde (fuer Nachvollziehbarkeit)
```
### 7.4 Neue Tabelle: `outreach_campaigns`
```sql
CREATE TABLE IF NOT EXISTS outreach_campaigns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
-- z.B. "Mario Leads April 2026" oder "Mallorca Hotels Q2"
icp TEXT NOT NULL,
-- AG / LP / beide
source_file TEXT,
-- Originale Excel-Datei
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
contacts_total INTEGER DEFAULT 0,
contacts_enriched INTEGER DEFAULT 0,
contacts_sent INTEGER DEFAULT 0,
contacts_replied INTEGER DEFAULT 0,
contacts_converted INTEGER DEFAULT 0
);
-- Verknuepfung Kontakt <-> Kampagne
ALTER TABLE cold_contacts ADD COLUMN campaign_id INTEGER REFERENCES outreach_campaigns(id);
```
### 7.5 Neue Tabelle: `enrichment_cache`
Um doppeltes Website-Scraping zu vermeiden:
```sql
CREATE TABLE IF NOT EXISTS enrichment_cache (
domain TEXT PRIMARY KEY,
scraped_text TEXT,
meta_title TEXT,
meta_description TEXT,
scraped_at DATETIME DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'success'
-- 'success' | 'failed' | 'timeout' | 'blocked'
);
```
### 7.6 Migration
Die Schema-Erweiterungen werden in `cold_outreach_db.py._init_schema()` integriert. SQLite unterstuetzt `ALTER TABLE ADD COLUMN` -- bestehende Daten bleiben erhalten. Die Migration laeuft automatisch beim naechsten Start.
```python
# In _init_schema() ergaenzen:
def _migrate_schema(self):
"""Fuegt neue Spalten hinzu falls sie fehlen."""
with self.get_connection() as conn:
existing = {row[1] for row in conn.execute("PRAGMA table_info(cold_contacts)")}
migrations = {
'website': 'TEXT',
'phone': 'TEXT',
'company_description': 'TEXT',
'enrichment_source': 'TEXT',
'enrichment_date': 'DATETIME',
'icp_confidence': 'REAL DEFAULT 0.0',
'icp_recommended_product': 'TEXT',
'language': "TEXT DEFAULT 'de'",
'campaign_id': 'INTEGER',
}
for col, col_type in migrations.items():
if col not in existing:
conn.execute(f"ALTER TABLE cold_contacts ADD COLUMN {col} {col_type}")
conn.commit()
```
---
## 8. Qualitaetssicherung
### 8.1 Drei-Stufen-Qualitaetskontrolle
**Stufe 1: Claude Self-Check (automatisch)**
Jede generierte Email bekommt von Claude selbst einen `personalization_score` (0.0-1.0) und eine Liste `personalization_elements`. Emails mit Score < 0.6 werden automatisch abgelehnt.
Was zaehlt als Personalisierungs-Element:
- Firmenname im Text (nicht nur in der Anrede)
- Branchenspezifisches Problem benannt
- Lokaler Bezug (Mallorca, Balearen)
- Konkreter Nutzen mit Zahl (Prozent, Euro, Zeit)
- Bezug auf reale Situation (Website-Info, Gaestebewertung, etc.)
**Stufe 2: Automatische Regel-Pruefung (Code)**
```python
def validate_email(subject: str, body: str, contact: dict) -> list[str]:
"""Prueft Email gegen harte Regeln. Gibt Liste von Problemen zurueck."""
problems = []
# Laenge
word_count = len(body.split())
if word_count > 200:
problems.append(f"Zu lang: {word_count} Woerter (max 200)")
if word_count < 40:
problems.append(f"Zu kurz: {word_count} Woerter (min 40)")
# Blacklisted Phrasen
BLACKLIST = [
"ich hoffe diese email erreicht sie",
"i hope this email finds you",
"als marktfuehrer",
"wir sind die nummer eins",
"exklusives angebot",
"nur fuer kurze zeit",
"unverbindlich und kostenlos",
"revolutionaer",
"game-changer",
"synergie",
]
body_lower = body.lower()
for phrase in BLACKLIST:
if phrase in body_lower:
problems.append(f"Blacklisted Phrase: '{phrase}'")
# Firmenname muss vorkommen (nicht nur in Anrede)
if contact.get('company'):
if contact['company'].lower() not in body_lower:
problems.append("Firmenname nicht im Text")
# Anrede pruefen
if contact.get('first_name'):
if contact['first_name'] not in body:
problems.append("Vorname nicht in Anrede")
# Call-to-Action (Fragezeichen am Ende)
if '?' not in body:
problems.append("Kein Call-to-Action (keine Frage)")
return problems
```
**Stufe 3: Manueller Review (Karlo)**
Jede Email wird von Karlo in der `review_cli.py` geprueft. Kein Versand ohne explizite Genehmigung. Das ist bewusst so -- bei Kaltakquise ist Qualitaet wichtiger als Geschwindigkeit.
### 8.2 Anti-Spam-Massnahmen
- **Sending Limit:** Maximal 50 Emails pro Tag (SendGrid Warmup)
- **Sending Window:** Nur Dienstag-Donnerstag, 9:00-11:00 Uhr (beste Oeffnungsraten)
- **Delay zwischen Emails:** Mindestens 30 Sekunden (wirkt nicht wie Massenversand)
- **Unsubscribe-Link:** Jede Email enthaelt einen Abmelde-Link (DSGVO-Pflicht)
- **SPF/DKIM/DMARC:** Muss fuer die Absender-Domain konfiguriert sein
- **Bounce-Handling:** SendGrid Webhook fuer Bounces --> Kontakt auf `status='bounced'`
### 8.3 Metriken und Feedback-Loop
```python
# In outreach_orchestrator.py
def get_campaign_stats(campaign_id: int) -> dict:
return {
'total_contacts': ...,
'emails_sent': ...,
'emails_opened': ..., # SendGrid Event Webhook (optional)
'replies_received': ...,
'reply_rate_pct': ...,
'conversions': ...,
'avg_personalization_score': ...,
'best_performing_industry': ..., # Welche Branche antwortet am meisten
'best_performing_step': ..., # Step 1, 2 oder 3
}
```
Wenn Reply-Rate unter 5% faellt: Prompt anpassen, Templates ueberarbeiten. Wenn eine Branche besonders gut antwortet: Mehr Kontakte in dieser Branche importieren.
### 8.4 DSGVO-Compliance
- Kaltakquise-Emails an Geschaeftskontakte sind unter bestimmten Bedingungen legal (B2B, berechtigtes Interesse)
- Jede Email braucht: Impressum, Abmelde-Moeglichkeit, echte Absenderadresse
- Kontaktdaten werden nur so lange gespeichert wie noetig
- Bei Abmeldung: Sofortige Sperre, keine weiteren Emails
- Dokumentation: Wer hat wann welche Email bekommen (cold_sequences Tabelle)
---
## Zusammenfassung: Reihenfolge der Implementierung
| Schritt | Modul | Abhaengigkeit | Aufwand |
|---------|-------|---------------|---------|
| 1 | `cold_outreach_db.py` Schema-Erweiterung | Keine | 2h |
| 2 | `excel_importer.py` | Schema | 3h |
| 3 | `contact_enricher.py` | Schema + Importer | 5h |
| 4 | `icp_classifier.py` | Schema + Enricher | 2h |
| 5 | `knowledge_retriever.py` | YouTube Research DB | 2h |
| 6 | `outreach_generator.py` | Alles oben | 6h |
| 7 | `review_cli.py` | Generator | 3h |
| 8 | `outreach_orchestrator.py` | Alles oben | 4h |
| 9 | `agent.py` Reply-Detection | Schema | 1h |
| **Gesamt** | | | **~28h** |
Empfehlung: Schritt 1-4 zuerst bauen und mit Marios echter Excel testen. Erst wenn die Daten sauber sind (Enrichment funktioniert), den Generator und Versand bauen.