Explorer
/tmp/restic-stage/lead-engine/OUTREACH_AGENT_KONZEPT.md
← Zurück ↓ Download
# 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.