Files
Brancheneinstufung2/lead-engine
Floke 9485cd4428 feat(lead-engine): setup isolated webhook test environment [31f88f42]
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.
2026-03-16 15:08:19 +00:00
..

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.de via 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.melcer auf 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@ mit e.melcer als Teilnehmer.

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.

  1. Vorbereiten der docker-compose.test.yml: Stellen Sie sicher, dass die Datei docker-compose.test.yml im 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: {}
    
  2. Vorbereiten der nginx-proxy-test.conf: Stellen Sie sicher, dass die Datei nginx-proxy-test.conf im 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;
            }
        }
    }
    
  3. Log-Datei vorbereiten: Erstellen Sie das Log-Verzeichnis und die Datei im lead-engine Kontext (falls noch nicht geschehen):

    mkdir -p lead-engine/Log && touch lead-engine/Log/smartlead_webhooks.log && chmod 777 lead-engine/Log/smartlead_webhooks.log
    
  4. 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)

  1. 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-lead Wenn Sie HTTPS nutzen, ändern Sie http zu https und verwenden Sie Ihren Domainnamen.
  2. Test-Auslösung bestätigen: Informieren Sie Gemini, sobald Smartlead die Test-Auslösung bestätigt hat.

  3. Log-Daten analysieren: Nach Erhalt der Test-Events lesen wir die Datei /app/lead-engine/Log/smartlead_webhooks.log aus, 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.

  1. Externe Erreichbarkeit:

    • Domain: gtm.robo-planet.de
    • Protokoll: HTTPS (Port 443)
    • Öffentliche Pfade: /smartlead/hot-lead und /smartlead/follow-up-lead
  2. 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.
  3. Nginx-Konfiguration (Externer Server gtm.robo-planet.de): Der externe Nginx-Server muss um einen location-Block erweitert werden, der dem folgenden Beispiel ähnelt (die genaue proxy_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;
    }
    
  4. 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.py scheiterte oft mit TimeZoneNotSupportedException.
  • 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 payload des API-Aufrufs mitgegeben.

2. Exchange AppOnly AccessPolicy (Buchungs-Workaround)

  • Problem: Calendars.ReadWrite erlaubt 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. Die send_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 von e.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_URL in der .env fü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 in nginx-proxy-clean.conf für /feedback/ war zu starr.

  • Lösung: Die Konfiguration wurde mit einer rewrite-Regel angepasst, um sicherzustellen, dass der gesamte Pfad korrekt an den lead-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.conf muss der Nginx-Container neu gestartet werden: docker-compose restart nginx

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 SuperOfficeClient in manager.py verursachte einen SyntaxError, da Bindestriche in Python-Modulnamen nicht erlaubt sind.

  • Lösung: Die fehlerhafte sys.path.append-Anweisung und die ungültige import-Zeile wurden aus lead-engine/trading_twins/manager.py entfernt, da sie Teil eines verschobenen Tasks waren.

    • Aktion nach Änderung: Nach der Korrektur in manager.py muss der lead-engine-Container neu gebaut und neu gestartet werden: docker-compose up -d --build --force-recreate lead-engine

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_lead lö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):
    1. Die E-Mail-Adresse der geteilten Bookings-Seite (KennenlernenmitRoboplanet@wackler-group.de) muss von der IT zur bestehenden Exchange ApplicationAccessPolicy der CAL_APPID hinzugefügt werden.
    2. Dadurch kann unsere bestehende "Lese-App" auch die Termine dieses zentralen Postfachs über die MS Graph API (GET /users/{bookings-email}/calendar/events) auslesen.
    3. Ein geplanter Job (z.B. täglich oder wöchentlich) zählt die neu hinzugekommenen Termine und erstellt einen kurzen KPI-Report.

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-superoffice an 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/