Created a dedicated test setup for Smartlead webhooks using docker-compose.test.yml and nginx-proxy-test.conf. This ensures a stable, minimal environment. Updated lead-engine/README.md with comprehensive instructions for local testing and production requirements for the Wackler IT.
Lead Engine: Multi-Source Automation v2.2 [31988f42]
🚀 Übersicht
Die Lead Engine ist ein spezialisiertes Modul zur autonomen Verarbeitung von B2B-Anfragen. Sie fungiert als Brücke zwischen dem E-Mail-Postfach und dem Company Explorer, um innerhalb von Minuten hochgradig personalisierte Antwort-Entwürfe auf "Human Expert Level" zu generieren.
🛠 Hauptfunktionen
1. Intelligenter E-Mail Ingest
- Multi-Source: Überwacht das Postfach
info@robo-planet.devia Microsoft Graph API. - Filter & Routing: Unterscheidet Anfragen von TradingTwins und dem Kontaktformular.
- Parsing: Spezialisierte HTML-Parser extrahieren strukturierte Daten (Firma, Kontakt, Bedarf).
2. Contact Research (LinkedIn Lookup)
- Automatisierung: Sucht via SerpAPI und Gemini 2.0 Flash nach der beruflichen Position.
- Ergebnis: Identifiziert Rollen (z.B. "CFO"), um den Tonfall anzupassen.
3. Company Explorer Sync & Monitoring
- Integration: Legt Accounts und Kontakte automatisch im CE an.
- Monitor: Hintergrund-Prozess (
monitor.py) überwacht den Analyse-Status. - Daten-Pull: Übernimmt Branche und Dossier in die lokale Lead-Datenbank.
4. Expert Response Generator
- KI-Engine: Gemini 2.0 Flash erstellt E-Mail-Entwürfe.
- Kontext: Kombiniert Lead-Daten + CE-Daten + Matrix-Argumente (Pains/Gains).
5. Trading Twins Autopilot (PRODUKTIV v2.2)
Der vollautomatische "Zero Touch" Workflow für Trading Twins Anfragen.
- Human-in-the-Loop: Elizabeta Melcer erhält eine Teams-Nachricht ("Approve/Deny").
- Feedback-Server: Ein integrierter FastAPI-Server (Port 8004) verarbeitet Klicks.
- Direct Calendar Booking (Micro-Service):
- Logik: Prüft den Kalender von
e.melcerauf echte Verfügbarkeit. - Raster: Termine starten nur im 15-Minuten-Takt (:00, :15, :30, :45).
- Abstand: Bietet zwei Termine an, mit ca. 3 Stunden Pause dazwischen.
- Buchung: Klick auf Link -> Server erstellt Outlook-Termin von
info@mite.melcerals Teilnehmer.
- Logik: Prüft den Kalender von
6. Smartlead Webhook Test & Integration [31f88f42]
🚀 Übersicht
Dieses Modul ist für den Empfang von Echtzeit-Lead-Benachrichtigungen direkt aus der Smartlead-Plattform konzipiert. Es verarbeitet eingehende Daten von Hot Leads und Follow-up Leads, um automatisierte Prozesse in unserer Infrastruktur anzustoßen.
Endpunkte
Die folgenden Endpunkte werden für Smartlead bereitgestellt. Der Platzhalter {IHRE_AKTUELLE_DOMAIN} sollte durch die aktuell aktive URL Ihrer Diskstation (z.B. floke-ai.duckdns.org:8090) oder später durch die produktive Domain ersetzt werden. Beachten Sie, dass für den Produktivbetrieb bei Wackler HTTPS obligatorisch sein wird.
- Hot Leads:
http(s)://{IHRE_AKTUELLE_DOMAIN}/public/smartlead/hot-lead - Follow-up Leads:
http(s)://{IHRE_AKTUELLE_DOMAIN}/public/smartlead/follow-up-lead
Lokales Test-Setup (Diskstation)
Um die Smartlead-Webhooks auf Ihrer Diskstation zu testen, stellen Sie sicher, dass nur die notwendigen Dienste aktiv sind. Hierfür verwenden wir eine schlanke docker-compose.test.yml und eine angepasste Nginx-Konfiguration.
-
Vorbereiten der
docker-compose.test.yml: Stellen Sie sicher, dass die Dateidocker-compose.test.ymlim Projekt-Root-Verzeichnis existiert und folgenden Inhalt hat (wurde bereits von Gemini erstellt):version: '3.8' services: nginx: image: nginx:alpine container_name: gateway_proxy restart: unless-stopped ports: - "8090:80" volumes: - ./nginx-proxy-test.conf:/etc/nginx/nginx.conf:ro - ./.htpasswd:/etc/nginx/.htpasswd:ro depends_on: dashboard: condition: service_started company-explorer: condition: service_healthy lead-engine: condition: service_started dashboard: image: nginx:alpine container_name: dashboard restart: unless-stopped volumes: - ./dashboard:/usr/share/nginx/html:ro company-explorer: build: context: ./company-explorer dockerfile: Dockerfile container_name: company-explorer restart: unless-stopped ports: - "8000:8000" environment: API_USER: "admin" API_PASSWORD: "gemini" PYTHONUNBUFFERED: "1" DATABASE_URL: "sqlite:////data/companies_v3_fixed_2.db" GEMINI_API_KEY: "${GEMINI_API_KEY}" SERP_API_KEY: "${SERP_API}" NOTION_TOKEN: "${NOTION_API_KEY}" volumes: - ./company-explorer:/app - explorer_db_data:/data - ./Log_from_docker:/app/logs_debug healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/docs"] interval: 10s timeout: 5s retries: 5 start_period: 30s lead-engine: build: context: ./lead-engine dockerfile: Dockerfile container_name: lead-engine restart: unless-stopped ports: - "8501:8501" # UI (Streamlit) - "8004:8004" # API / Monitor - "8099:8004" # Direct Test Port environment: PYTHONUNBUFFERED: "1" GEMINI_API_KEY: "${GEMINI_API_KEY}" SERP_API: "${SERP_API}" INFO_Application_ID: "${INFO_Application_ID}" INFO_Tenant_ID: "${INFO_Tenant_ID}" INFO_Secret: "${INFO_Secret}" CAL_APPID: "${CAL_APPID}" CAL_SECRET: "${CAL_SECRET}" CAL_TENNANT_ID: "${CAL_TENNANT_ID}" TEAMS_WEBHOOK_URL: "${TEAMS_WEBHOOK_URL}" FEEDBACK_SERVER_BASE_URL: "${FEEDBACK_SERVER_BASE_URL}" WORDPRESS_BOOKING_URL: "${WORDPRESS_BOOKING_URL}" MS_BOOKINGS_URL: "${MS_BOOKINGS_URL}" volumes: - ./lead-engine:/app - lead_engine_data:/app/data volumes: explorer_db_data: {} lead_engine_data: {} -
Vorbereiten der
nginx-proxy-test.conf: Stellen Sie sicher, dass die Dateinginx-proxy-test.confim Projekt-Root-Verzeichnis existiert und folgenden Inhalt hat (wurde bereits von Gemini erstellt):events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; access_log /dev/stdout; error_log /dev/stderr; client_max_body_size 50M; proxy_read_timeout 1200s; proxy_connect_timeout 1200s; proxy_send_timeout 1200s; send_timeout 1200s; resolver 127.0.0.11 valid=30s ipv6=off; server { listen 80; location / { auth_basic "Restricted Access - Local AI Suite"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://dashboard:80; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /ce/ { auth_basic "Restricted Access - Local AI Suite"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://company-explorer:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location /lead/ { auth_basic "Restricted Access - Local AI Suite"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://lead-engine:8501/;\ proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_http_version 1.1; proxy_read_timeout 86400; } location /feedback/ { auth_basic off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; rewrite ^/feedback/(.*)$ /$1 break; proxy_pass http://lead-engine:8004; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /public/smartlead/ { auth_basic off; rewrite ^/public/smartlead/(.*)$ /webhook/$1 break; proxy_pass http://lead-engine:8004; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } } -
Log-Datei vorbereiten: Erstellen Sie das Log-Verzeichnis und die Datei im
lead-engineKontext (falls noch nicht geschehen):mkdir -p lead-engine/Log && touch lead-engine/Log/smartlead_webhooks.log && chmod 777 lead-engine/Log/smartlead_webhooks.log -
Test-Setup starten: Um das lokale Test-Setup zu starten, führen Sie diese Befehle aus:
# Bestehende (potenziell fehlerhafte) Container stoppen und entfernen docker-compose -f docker-compose.test.yml down # Neues, schlankes Setup starten docker-compose -f docker-compose.test.yml up -d
Test-Prozess & Datenanalyse (Diskstation)
-
URLs an Smartlead übergeben: Geben Sie die folgenden URLs an Smartlead weiter, um Test-Events auszulösen. Ersetzen Sie
<Ihre_Diskstation_IP_oder_Domain>durch die tatsächliche IP/Domain Ihrer Diskstation:- Hot Lead Webhook:
http://<Ihre_Diskstation_IP_oder_Domain>:8090/public/smartlead/hot-lead - Follow-up Lead Webhook:
http://<Ihre_Diskstation_IP_oder_Domain>:8090/public/smartlead/follow-up-leadWenn Sie HTTPS nutzen, ändern Siehttpzuhttpsund verwenden Sie Ihren Domainnamen.
- Hot Lead Webhook:
-
Test-Auslösung bestätigen: Informieren Sie Gemini, sobald Smartlead die Test-Auslösung bestätigt hat.
-
Log-Daten analysieren: Nach Erhalt der Test-Events lesen wir die Datei
/app/lead-engine/Log/smartlead_webhooks.logaus, um die exakte JSON-Struktur der Payloads und die Quell-IP-Adressen von Smartlead zu ermitteln.
Anforderungen für Wackler IT (Produktivumgebung)
Basierend auf den Analysen aus dem lokalen Test-Setup werden wir die Anforderungen an die IT-Abteilung von Wackler formulieren, um die Smartlead-Webhooks in der Produktivumgebung sicher freizuschalten.
-
Externe Erreichbarkeit:
- Domain:
gtm.robo-planet.de - Protokoll: HTTPS (Port 443)
- Öffentliche Pfade:
/smartlead/hot-leadund/smartlead/follow-up-lead
- Domain:
-
Firewall-Regel (Quell-IPs):
- Smartlead sendet Webhooks von spezifischen IP-Adressen (diese werden aus den Test-Logs ermittelt). Die Firewall muss so konfiguriert werden, dass nur Anfragen von diesen erlaubten Smartlead-IPs den externen Nginx-Server erreichen dürfen. Alle anderen IPs sind zu blockieren.
-
Nginx-Konfiguration (Externer Server
gtm.robo-planet.de): Der externe Nginx-Server muss um einenlocation-Block erweitert werden, der dem folgenden Beispiel ähnelt (die genaueproxy_pass-IP (10.10.81.2) sollte von der Wackler IT bestätigt werden, da dies die interne IP Ihres GTM-Gateways im Wackler-Netzwerk ist):location /smartlead/ { auth_basic off; # Keine Authentifizierung für öffentliche Webhooks # Erlaubte Smartlead-IPs (Platzhalter - aus Test-Logs zu befüllen) # Beispiel: # allow 192.0.2.0/24; # allow 203.0.113.0/24; deny all; # Alle anderen IPs blockieren rewrite ^/smartlead/(.*)$ /public/smartlead/$1 break; proxy_pass http://10.10.81.2:8090; # Weiterleitung zum internen GTM-Gateway (Port 8090) # Standard Proxy Header proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } -
Wichtiger Sicherheitshinweis: Es muss explizit sichergestellt werden, dass der SuperOffice Connector in der Produktivumgebung nicht gestartet oder so konfiguriert ist, dass er keine Verbindung zu Wacklers SuperOffice-System herstellt, um Datenkonflikte zu vermeiden.
🏗 Architektur
/app/lead-engine/
├── app.py # Streamlit Web-Interface
├── trading_twins_ingest.py # E-Mail Importer (Graph API)
├── monitor.py # Monitor + Trigger für Orchestrator
├── trading_twins/ # Autopilot Modul
│ ├── manager.py # Orchestrator, FastAPI, Graph API Logic
│ ├── test_calendar_logic.py # Interner Test für Kalender-Zugriff
│ └── signature.html # HTML-Signatur (mit Bildern im selben Ordner)
└── db.py # Lokale SQLite Lead-Datenbank
🚨 Lessons Learned & Critical Fixes
1. Microsoft Graph API: Kalender-Zugriff
- Problem:
debug_calendar.pyscheiterte oft mitTimeZoneNotSupportedException. - Ursache: Der API-Aufruf zur Abfrage der Verfügbarkeit (
getSchedule) hat keine explizite Zeitzoneninformation erhalten. - Lösung: Die Zeitzone ("Europe/Berlin") wird nun explizit im
payloaddes API-Aufrufs mitgegeben.
2. Exchange AppOnly AccessPolicy (Buchungs-Workaround)
- Problem:
Calendars.ReadWriteerlaubt einer App oft nicht, Termine in fremden Kalendern (e.melcer@) zu erstellen (403 Forbidden). - Lösung: Der Termin wird im eigenen Kalender des Service-Accounts (
info@) erstellt. Der Mitarbeiter (e.melcer@) wird als Teilnehmer hinzugefügt. Das umgeht die Policy.
3. Dynamische HTML-Signatur mit Inline-Bildern
- Problem: Eine statische Signatur in der Konfiguration war unflexibel und konnte keine Bilder enthalten.
- Lösung: Ein Skript (
scripts/extract_signature_assets.py) extrahiert die vollständige HTML-Signatur und alle eingebetteten Bilder aus einer.eml-Datei. Diesend_email-Funktion wurde überarbeitet, um alle Bilder dynamisch als Inline-Anhänge zu versenden, was eine professionelle Darstellung sicherstellt.
4. Race-Condition-Schutz bei Überbuchung (Live-Check)
- Problem: Wenn mehrere Leads E-Mails mit denselben Terminvorschlägen erhalten, konnten Doppelbuchungen entstehen.
- Lösung: Implementierung eines "Live-Checks" im Feedback-Server. Bevor ein Termin gebucht wird, prüft das System in Echtzeit (
is_slot_free), ob der Slot im Kalender vone.melcer@noch verfügbar ist. Ist er belegt, wird die Buchung abgebrochen und ein Fallback-Szenario aktiviert.
5. Seamless Website Integration (WordPress iFrame)
- Problem: Die API-Endpoints gaben nackten Text zurück, was für Kunden unprofessionell wirkte.
- Lösung: Der Server liefert nun saubere HTML-Snippets zurück. Durch Setzen der
WORDPRESS_BOOKING_URLin der.envführen die Links in der Kunden-E-Mail direkt auf die Kunden-Website. Dort wird das HTML-Ergebnis (Erfolg inkl. Teams-Link ODER Fallback zum Microsoft Bookings Kalender bei Doppelbuchung) unsichtbar in einem iFrame geladen.
5.1 Troubleshooting der iFrame-Integration
Um sicherzustellen, dass die Buchungs-Landingpage auf WordPress immer korrekt funktioniert – sowohl mit spezifischen Terminvorschlägen als auch als Fallback zu einer allgemeinen Buchungsseite – sind mehrere Konfigurationsschritte und Fehlerbehebungen notwendig:
1. Robuster iFrame-Code für Ihre WordPress-Seite
Der folgende JavaScript-Code sollte in Ihre WordPress-Seite integriert werden, die als Landingpage für Terminbuchungen dient (z.B. robo-planet.de/terminbestaetigung). Er prüft, ob spezifische Termindaten (Job-UUID und Timestamp) über die URL übergeben werden, und lädt entsprechend entweder den spezifischen Termin-Slot oder die allgemeine Microsoft Bookings URL als Fallback.
<iframe id="booking-iframe" width="100%" height="800px" style="border:none;" scrolling="no"></iframe>
<script>
const urlParams = new URLSearchParams(window.location.search);
const jobUuid = urlParams.get('job_uuid');
const timestamp = urlParams.get('ts');
let iframeSrc = "";
if (jobUuid && timestamp) {
// Wenn beide Parameter vorhanden sind, den spezifischen Buchungslink verwenden
iframeSrc = "https://floke-ai.duckdns.org/feedback/book_slot/" + jobUuid + "/" + timestamp;
} else {
// Andernfalls auf die allgemeine MS Bookings URL verweisen
iframeSrc = "https://outlook.office.com/book/KennenlernenmitRoboplanet@wackler-group.de/?ismsaljsauthenabled";
}
document.getElementById('booking-iframe').src = iframeSrc;
</script>
2. Nginx-Konfiguration für korrekte Pfad-Weiterleitung
Das Laden der spezifischen /feedback/book_slot/...-URL im iFrame scheiterte initial, da der Nginx-Proxy die verschachtelten Pfade nicht korrekt an den lead-engine-Container weiterleitete. Dies führte zur "Synology-Fehlerseite".
-
Problem: Die
location-Regel innginx-proxy-clean.conffür/feedback/war zu starr. -
Lösung: Die Konfiguration wurde mit einer
rewrite-Regel angepasst, um sicherzustellen, dass der gesamte Pfad korrekt an denlead-engine-Dienst weitergegeben wird und die notwendigen Proxy-Header für moderne Webanwendungen gesetzt sind.location /feedback/ { auth_basic off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; rewrite ^/feedback/(.*)$ /$1 break; proxy_pass http://lead-engine:8004; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }- Aktion nach Änderung: Nach dem Anpassen von
nginx-proxy-clean.confmuss der Nginx-Container neu gestartet werden:docker-compose restart nginx
- Aktion nach Änderung: Nach dem Anpassen von
3. Behebung des lead-engine Service-Absturzes (SyntaxError)
Der lead-engine-Dienst selbst stürzte aufgrund eines Python SyntaxError ab, der durch einen ungültigen Import-Pfad verursacht wurde, der Bindestriche enthielt. Dies verhinderte, dass der Dienst überhaupt eine Antwort senden konnte, was ebenfalls zu einer leeren iFrame-Anzeige beitrug.
-
Problem: Eine Zeile
from connector-superoffice.superoffice_client import SuperOfficeClientinmanager.pyverursachte einenSyntaxError, da Bindestriche in Python-Modulnamen nicht erlaubt sind. -
Lösung: Die fehlerhafte
sys.path.append-Anweisung und die ungültigeimport-Zeile wurden auslead-engine/trading_twins/manager.pyentfernt, da sie Teil eines verschobenen Tasks waren.- Aktion nach Änderung: Nach der Korrektur in
manager.pymuss derlead-engine-Container neu gebaut und neu gestartet werden:docker-compose up -d --build --force-recreate lead-engine
- Aktion nach Änderung: Nach der Korrektur in
Durch diese Maßnahmen wird die iFrame-Integration nun robust funktionieren und immer eine sinnvolle Ansicht auf der WordPress-Landingpage bieten.
🚀 Inbetriebnahme & Test
Inbetriebnahme
# Neustart des Dienstes
docker-compose up -d --build --force-recreate lead-engine
Test & Debugging
- Allgemeiner Test: Die URL
https://floke-ai.duckdns.org/feedback/test_leadlöst einen generischen Test-Lead aus. - Spezifischer Test pro Lead: Im Lead-Tool (
/lead/) kann für jeden Lead mit einem generierten E-Mail-Entwurf der Button "🧪 Test-Versand (an floke.com@gmail.com)" geklickt werden. Dies startet den gesamten End-to-End-Prozess (Teams-Nachricht & E-Mail-Versand) für den ausgewählten Lead, sendet die E-Mail aber sicher an die Test-Adresse.
Zugriff: https://floke-ai.duckdns.org/lead/ (Passwortgeschützt)
📝 Zukünftige Erweiterungen & Todos
Task: Automatisches Nachfassen (Follow-up)
- Problem: Wenn ein Lead nicht auf die E-Mail antwortet und auch keinen Termin bucht, geht der Kontakt verloren.
- Lösung: Einbindung eines Follow-up-Mechanismus nach 5 Tagen. Dies könnte entweder durch ein Flag im CRM-System oder durch eine geplante E-Mail direkt über die Lead-Engine realisiert werden.
Task: Erfolgsmessung & Tracking (Microsoft Bookings Auswertung)
- Ziel: Übersicht gewinnen, wie viele Meetings tatsächlich über diesen neuen Kanal (Trading Twins / E-Mail) final gebucht wurden.
- Lösungsweg (Pragmatischer Ansatz):
- Die E-Mail-Adresse der geteilten Bookings-Seite (
KennenlernenmitRoboplanet@wackler-group.de) muss von der IT zur bestehenden Exchange ApplicationAccessPolicy derCAL_APPIDhinzugefügt werden. - Dadurch kann unsere bestehende "Lese-App" auch die Termine dieses zentralen Postfachs über die MS Graph API (
GET /users/{bookings-email}/calendar/events) auslesen. - Ein geplanter Job (z.B. täglich oder wöchentlich) zählt die neu hinzugekommenen Termine und erstellt einen kurzen KPI-Report.
- Die E-Mail-Adresse der geteilten Bookings-Seite (
Task: CRM-Synchronisierung der gebuchten Termine (SuperOffice)
- Ziel: Die über Bookings generierten Termine müssen für die Historie und Dokumentation im CRM-System (SuperOffice) landen.
- Lösung: Die im vorherigen Task ausgelesenen Termine werden über den
connector-superofficean das CRM übergeben und dort als "Termin" oder "Aktivität" direkt beim entsprechenden Kontakt (gematcht über die E-Mail-Adresse) abgelegt.
# Info-Postfach (App 1 - Schreiben)
INFO_Application_ID=...
INFO_Tenant_ID=...
INFO_Secret=...
# E.Melcer Kalender (App 2 - Lesen)
CAL_APPID=...
CAL_TENNANT_ID=...
CAL_SECRET=...
# URLs
TEAMS_WEBHOOK_URL=...
FEEDBACK_SERVER_BASE_URL=https://floke-ai.duckdns.org/feedback
WORDPRESS_BOOKING_URL=https://www.robo-planet.de/terminbestaetigung
MS_BOOKINGS_URL=https://outlook.office365.com/book/KennenlernenmitRoboplanet@wackler-group.de/