293 lines
16 KiB
Markdown
293 lines
16 KiB
Markdown
# SuperOffice Connector & GTM Engine ("The Muscle & The Brain")
|
||
|
||
Dieses Dokument beschreibt die Architektur der **Go-to-Market (GTM) Engine**, die SuperOffice CRM mit der Company Explorer Intelligence verbindet.
|
||
|
||
Ziel des Systems ist der vollautomatisierte Versand von **hyper-personalisierten E-Mails**, die so wirken, als wären sie manuell von einem Branchenexperten geschrieben worden.
|
||
|
||
---
|
||
|
||
## 1. Das Konzept: "Static Magic"
|
||
|
||
Anders als bei üblichen KI-Tools, die E-Mails "on the fly" generieren, setzt dieses System auf **vorberechnete, statische Textbausteine**.
|
||
|
||
**Warum?**
|
||
1. **Qualitätssicherung:** Jeder Baustein kann vor dem Versand geprüft werden.
|
||
2. **Performance:** SuperOffice muss beim Versand keine KI anfragen, sondern nur Felder zusammenfügen.
|
||
3. **Konsistenz:** Ein "Finanzleiter im Maschinenbau" bekommt immer dieselbe, perfekte Argumentation – egal bei welchem Unternehmen.
|
||
|
||
### Die E-Mail-Formel
|
||
|
||
Eine E-Mail setzt sich aus **drei statischen Komponenten** zusammen, die im CRM (SuperOffice) gespeichert sind:
|
||
|
||
```text
|
||
[1. Opener (Unternehmens-Spezifisch)] + [2. Bridge (Persona x Vertical)] + [3. Social Proof (Vertical)]
|
||
```
|
||
|
||
* **1. Opener (Der Haken):** Bezieht sich zu 100% auf das spezifische Unternehmen und dessen Geschäftsmodell.
|
||
* *Quelle:* `Company`-Objekt (Feld: `ai_opener`).
|
||
* *Beispiel:* "Die präzise Just-in-Time-Fertigung von **Müller CNC** erfordert einen reibungslosen Materialfluss ohne Mikrostillstände."
|
||
* **2. Bridge (Die Relevanz):** Holt die Person in ihrer Rolle ab und verknüpft sie mit dem Branchen-Pain.
|
||
* *Quelle:* `Matrix`-Tabelle (Feld: `intro`).
|
||
* *Beispiel:* "Für Sie als **Produktionsleiter** bedeutet das, trotz Fachkräftemangel die Taktzeiten an der Linie stabil zu halten."
|
||
* **3. Social Proof (Die Lösung):** Zeigt Referenzen und den konkreten Nutzen (Gains).
|
||
* *Quelle:* `Matrix`-Tabelle (Feld: `social_proof`).
|
||
* *Beispiel:* "Unternehmen wie **Jungheinrich** nutzen unsere Transportroboter, um Fachkräfte an der Maschine zu halten und Suchzeiten um 30% zu senken."
|
||
|
||
---
|
||
|
||
## 2. Die Datenbasis (Foundation)
|
||
|
||
Die Qualität der Texte steht und fällt mit der Datenbasis. Diese wird zentral in **Notion** gepflegt und in den Company Explorer synchronisiert.
|
||
|
||
### A. Verticals (Branchen)
|
||
Definiert die **Makro-Pains** und **Gains** einer Branche sowie das **passende Produkt**.
|
||
* *Beispiel:* Healthcare -> Pain: "Pflegekräfte machen Logistik" -> Gain: "Hände fürs Bett" -> Produkt: Service-Roboter.
|
||
* *Wichtig:* Unterscheidung nach **Ops-Focus** (Operativ vs. Infrastruktur) steuert das Produkt (Reinigung vs. Service).
|
||
|
||
### B. Personas (Rollen)
|
||
Definiert die **persönlichen Pains** einer Rolle.
|
||
* *Beispiel:* Produktionsleiter -> Pain: "OEE / Taktzeit".
|
||
* *Beispiel:* Geschäftsführer -> Pain: "ROI / Amortisation".
|
||
|
||
---
|
||
|
||
## 3. Die Matrix-Engine (Multiplikation)
|
||
|
||
Das Skript `generate_matrix.py` (im Backend) ist das Herzstück. Es berechnet **alle möglichen Kombinationen** aus Verticals und Personas voraus.
|
||
|
||
**Logik:**
|
||
1. Lade alle Verticals (`V`) und Personas (`P`).
|
||
2. Für jede Kombination `V x P`:
|
||
* Lade `V.Pains` und `P.Pains`.
|
||
* Generiere via Gemini einen **perfekten Satz 2 (Bridge)** und **Satz 3 (Proof)**.
|
||
* Generiere ein **Subject**, das den Persona-Pain trifft.
|
||
3. Speichere das Ergebnis in der Tabelle `marketing_matrix`.
|
||
|
||
*Ergebnis:* Eine Lookup-Tabelle, aus der für jeden Kontakt sofort der passende Text gezogen werden kann.
|
||
|
||
---
|
||
|
||
## 4. Der "Opener" (First Sentence)
|
||
|
||
Dieser Baustein ist der einzige, der **pro Unternehmen** generiert wird (bei der Analyse/Discovery).
|
||
|
||
**Logik:**
|
||
1. Scrape Website-Content.
|
||
2. Identifiziere das **Vertical** (z.B. Maschinenbau).
|
||
3. Lade den **Core-Pain** des Verticals (z.B. "Materialfluss").
|
||
4. **Prompt:** "Analysiere das Geschäftsmodell von [Firma]. Formuliere einen Satz, der erklärt, warum [Core-Pain] für genau dieses Geschäftsmodell kritisch ist."
|
||
|
||
*Ergebnis:* Ein Satz, der beweist: "Ich habe verstanden, was ihr tut."
|
||
|
||
---
|
||
|
||
## 5. SuperOffice Connector ("The Muscle")
|
||
|
||
Der Connector ist der Bote, der diese Daten in das CRM bringt.
|
||
|
||
**Workflow:**
|
||
1. **Trigger:** Kontakt-Änderung in SuperOffice (Webhook).
|
||
2. **Enrichment:** Connector fragt Company Explorer: "Gib mir Daten für Firma X, Person Y".
|
||
3. **Lookup:** Company Explorer...
|
||
* Holt den `Opener` aus der Company-Tabelle.
|
||
* Bestimmt `Vertical` und `Persona`.
|
||
* Sucht den passenden Eintrag in der `MarketingMatrix`.
|
||
4. **Write-Back:** Connector schreibt die Texte in die UDF-Felder (User Defined Fields) des Kontakts in SuperOffice.
|
||
* `UDF_Opener`
|
||
* `UDF_Bridge`
|
||
* `UDF_Proof`
|
||
* `UDF_Subject`
|
||
* `UDF_UnsubscribeLink`
|
||
|
||
---
|
||
|
||
### 6. Monitoring & Dashboard ("The Eyes")
|
||
|
||
Das System verfügt über ein integriertes Echtzeit-Dashboard zur Überwachung der Synchronisationsprozesse.
|
||
|
||
**Features:**
|
||
* **Account-basierte Ansicht:** Gruppiert alle Ereignisse nach SuperOffice-Account oder Person, um den aktuellen Status pro Datensatz zu zeigen.
|
||
* **Phasen-Visualisierung:** Stellt den Fortschritt in vier Phasen dar:
|
||
1. **Received:** Webhook erfolgreich empfangen.
|
||
2. **Enriching:** Datenanreicherung im Company Explorer läuft (Gelb blinkend = In Arbeit).
|
||
3. **Syncing:** Rückschreiben der Daten nach SuperOffice (Gelb blinkend = In Arbeit).
|
||
4. **Completed:** Prozess für diesen Kontakt erfolgreich abgeschlossen (Grün).
|
||
* **Performance-Tracking:** Anzeige der Gesamtdurchlaufzeit (Duration) pro Prozess.
|
||
* **Fehler-Analyse:** Detaillierte Fehlermeldungen direkt in der Übersicht.
|
||
* **Dark Mode:** Modernes UI-Design für Admin-Monitoring.
|
||
|
||
**Zugriff:**
|
||
Das Dashboard ist über das Company Explorer Frontend (Icon "Activity" im Header) oder direkt unter `/connector/dashboard` erreichbar.
|
||
|
||
---
|
||
|
||
## 7. Setup & Wartung
|
||
|
||
### Neue Branche hinzufügen
|
||
1. In **Notion** anlegen (Pains/Gains/Produkte definieren).
|
||
2. Sync-Skript laufen lassen: `python3 backend/scripts/sync_notion_industries.py`.
|
||
3. Matrix neu berechnen: `python3 backend/scripts/generate_matrix.py --live`.
|
||
|
||
### End-to-End Tests
|
||
Ein automatisierter Integrationstest (`tests/test_e2e_flow.py`) deckt den gesamten Zyklus ab:
|
||
1. **Company Creation:** Webhook -> CE Provisioning -> Write-back (Vertical).
|
||
2. **Person Creation:** Webhook -> CE Matrix Lookup -> Write-back (Texte).
|
||
3. **Vertical Change:** Änderung im CRM -> CE Update -> Cascade zu Personen -> Neue Texte.
|
||
|
||
Ausführen mittels:
|
||
```bash
|
||
python3 connector-superoffice/tests/test_e2e_flow.py
|
||
```
|
||
|
||
## 7. Troubleshooting & Known Issues
|
||
|
||
### Authentication "URL has an invalid label"
|
||
Tritt auf, wenn `SO_ENVIRONMENT` leer ist. Der Client fällt nun automatisch auf `sod` zurück.
|
||
|
||
### Pydantic V2 Compatibility
|
||
Die `config.py` wurde auf natives Python (`os.getenv`) umgestellt, um Konflikte mit `pydantic-settings` in Docker-Containern zu vermeiden.
|
||
|
||
### Address & VAT Sync (WIP)
|
||
Der Worker wurde erweitert, um auch `City` und `OrgNumber` (VAT) zurückzuschreiben.
|
||
**Status (21.02.2026):** Implementiert, aber noch im Feinschliff. Logs zeigen teils Re-Queueing während das Enrichment läuft.
|
||
|
||
### 8. Lessons Learned: Address & VAT Sync (Solved Feb 22, 2026)
|
||
|
||
Die Synchronisation von Adressdaten stellte sich als unerwartet komplex heraus. Hier die wichtigsten Erkenntnisse für zukünftige Entwickler:
|
||
|
||
1. **Das "OrgNumber"-Phantom:**
|
||
* **Problem:** In der API-Dokumentation oft als `OrgNumber` referenziert, akzeptiert die WebAPI (REST) strikt nur **`OrgNr`**.
|
||
* **Lösung:** Mapping im `worker.py` hart auf `OrgNr` umgestellt.
|
||
|
||
2. **Verschachtelte Adress-Struktur:**
|
||
* **Problem:** Ein flaches Update auf `PostalAddress` wird von der API stillschweigend ignoriert (HTTP 200, aber keine Änderung).
|
||
* **Lösung:** Das Update muss die tiefe Struktur respektieren: `Address["Postal"]["City"]` UND `Address["Street"]["City"]`. Beide müssen explizit gesetzt werden, um in der UI sichtbar zu sein.
|
||
|
||
3. **Die "Race Condition" Falle (Atomic Updates):**
|
||
* **Problem:** Wenn Adress-Daten (`PUT Contact`) und UDF-Daten (`PUT Contact/Udef`) in separaten API-Aufrufen kurz hintereinander gesendet werden, gewinnt der letzte Call. Da dieser oft auf einem *veralteten* `GET`-Stand basiert (bevor das erste Update durch war), wurde die Adresse wieder mit "Leer" überschrieben.
|
||
* **Lösung:** **Atomic Update Strategy**. Der Worker sammelt *alle* Änderungen (Adresse, VAT, Vertical, Openers) in einem einzigen Dictionary und sendet genau **einen** `PUT`-Request an den Kontakt-Endpunkt. Dies garantiert Konsistenz.
|
||
|
||
---
|
||
|
||
### 9. Lessons Learned: Appointment Simulation & Persona Matching
|
||
|
||
Die Simulation von E-Mails via Terminen (Appointments) erforderte Workarounds für das UI-Verhalten von SuperOffice.
|
||
|
||
1. **Header vs. Description (Die 42-Zeichen-Grenze):**
|
||
* SuperOffice nutzt im Kalender und in Listen den `MainHeader` als Titel. Dieser ist auf ca. 42 Zeichen begrenzt.
|
||
* Ist der Titel länger, schneidet SuperOffice ihn ab. Erscheint der Titel inkonsistent, "stiehlt" das UI oft die erste Zeile der `Description` als Titel-Ersatz.
|
||
* **Strategie:** Wir kürzen den `MainHeader` auf 40 Zeichen und stellen sicher, dass der **vollständige Betreff** als allererste Zeile in der `Description` steht. Danach folgen zwei Newlines. Damit landet der Betreff im UI-Header und die Anrede ("Hallo...") bleibt sicher im Textkörper.
|
||
|
||
2. **Mapping-Resilienz (Funktion vs. Titel):**
|
||
* Jobtitel (Funktion) landen in SuperOffice inkonsistent in den Feldern `JobTitle` oder `Title`.
|
||
* **Lösung:** Der Worker fragt nun beide Felder ab (`person.get("JobTitle") or person.get("Title")`), um die Rolle korrekt zuzuweisen.
|
||
|
||
3. **Rollen-Dynamik:**
|
||
* Um zu verhindern, dass alte Rollen (z.B. "Infrastruktur") nach einer Beförderung/Änderung in SuperOffice "kleben" bleiben, führt das System nun bei jeder Namens- oder Funktionsänderung einen **Rollen-Reset** durch.
|
||
|
||
---
|
||
|
||
### 10. Lessons Learned: API Optimization & Certification (Feb 24, 2026)
|
||
|
||
Um die Zertifizierung für den SuperOffice App Store zu erhalten, mussten kritische Performance-Optimierungen durchgeführt werden.
|
||
|
||
1. **Die `getAllRows`-Falle:**
|
||
* **Problem:** SuperOffice monierte in der Validierung API-Calls wie `getAllRows` (implizit oft durch Abfragen ganzer Objekte ohne Filter), die unnötige Last verursachen.
|
||
* **Lösung:** Implementierung von **OData `$select`**. Wir fordern nun strikt nur die Felder an, die wir wirklich benötigen (z.B. `get_person(id, select=['JobTitle', 'UserDefinedFields'])`).
|
||
* **Wichtig:** Niemals pauschal `get_person()` aufrufen, wenn nur die Rolle geprüft werden soll.
|
||
|
||
3. **PUT vs. PATCH (Safe Updates):**
|
||
* **Problem:** Die Verwendung von `PUT` zum Aktualisieren von Entitäten (Person/Contact) erfordert, dass das *gesamte* Objekt gesendet wird. Dies birgt das Risiko, Felder zu überschreiben, die zwischenzeitlich von anderen Benutzern geändert wurden ("Race Condition"), und verursacht unnötigen Traffic.
|
||
* **Lösung:** Umstellung auf **`PATCH`**. Wir senden nun nur noch die *tatsächlich geänderten Felder* (Delta).
|
||
* **Implementierung:** Der Worker baut nun ein `patch_payload` (z.B. `{'Position': {'Id': 123}}`) und nutzt den dedizierten PATCH-Endpunkt. Dies wurde explizit von SuperOffice für die Zertifizierung gefordert.
|
||
|
||
### 11. Production Environment (Live Feb 27, 2026)
|
||
|
||
Nach erfolgreicher Zertifizierung durch SuperOffice wurde der Connector auf die Produktionsumgebung umgestellt.
|
||
|
||
* **Tenant:** `Cust26720`
|
||
* **Environment:** `online3` (zuvor `sod`)
|
||
* **Endpoint:** `https://online3.superoffice.com/Cust26720/api/v1`
|
||
* **Authentication:** Umstellung auf Produktions-Client-ID und -Secret erfolgreich verifiziert (Health Check OK).
|
||
|
||
**Wichtig:** SuperOffice nutzt Load-Balancing. Die Subdomain (`online3`) kann sich theoretisch ändern. Die Anwendung prüft dies dynamisch, aber die Basis-Konfiguration sollte den aktuellen Tenant-Status widerspiegeln.
|
||
|
||
### 12. Lessons Learned: Production Migration (Feb 27, 2026)
|
||
|
||
Der Wechsel von der Staging-Umgebung (`sod`) zur Produktion (`onlineX`) brachte spezifische technische Hürden mit sich:
|
||
|
||
1. **Globaler Token-Endpunkt:**
|
||
* **Problem:** Mandantenspezifische Subdomains (wie `online3.superoffice.com`) akzeptieren oft keine OAuth-Anfragen oder liefern leere Antworten.
|
||
* **Lösung:** Für den Token-Refresh muss zwingend der globale Endpunkt **`https://online.superoffice.com/login/common/oauth/tokens`** verwendet werden, unabhängig davon, auf welchem Cluster der Mandant liegt.
|
||
|
||
2. **DNS-Präfixe (app- vs. direkt):**
|
||
* **Problem:** In der Staging-Umgebung lautet der API-Host meist `app-sod.superoffice.com`. In der Produktion wird das `app-` Präfix oft nicht verwendet oder führt zu Zertifikatsfehlern.
|
||
* **Lösung:** Der `SuperOfficeClient` wurde so flexibilisiert, dass er in der Produktion direkt auf `{env}.superoffice.com` zugreift.
|
||
|
||
3. **Environment Variables Persistence:**
|
||
* **Problem:** Docker-Container behalten Umgebungsvariablen oft im Cache ("Shadow Configuration"), selbst wenn die `.env`-Datei geändert wurde.
|
||
* **Lösung:** Zwingendes `docker-compose up -d --force-recreate` nach Credentials-Änderungen.
|
||
|
||
### 13. Post-Migration Configuration (Cust26720)
|
||
|
||
Die Konfiguration in der `.env` Datei wurde für die Produktion wie folgt finalisiert:
|
||
|
||
| Funktion | UDF / ID | Entity |
|
||
| :--- | :--- | :--- |
|
||
| **Subject** | `SuperOffice:19` | Person |
|
||
| **Intro Text** | `SuperOffice:20` | Person |
|
||
| **Social Proof** | `SuperOffice:21` | Person |
|
||
| **Unsubscribe** | `SuperOffice:22` | Person |
|
||
| **Campaign Tag** | `SuperOffice:23` | Person |
|
||
| **Opener Primary** | `SuperOffice:86` | Contact |
|
||
| **Opener Sec.** | `SuperOffice:87` | Contact |
|
||
| **Vertical** | `SuperOffice:83` | Contact |
|
||
| **Summary** | `SuperOffice:84` | Contact |
|
||
| **Last Update** | `SuperOffice:85` | Contact |
|
||
|
||
### 14. Kampagnen-Steuerung (Usage)
|
||
|
||
Das System unterstützt mehrere Outreach-Varianten über das Feld **`MA_Campaign`** (Person).
|
||
|
||
1. **Standard:** Bleibt das Feld leer, werden die Standard-Texte ("standard") für Kaltakquise geladen.
|
||
2. **Spezifisch:** Wird ein Wert gewählt (z.B. "Messe 2026"), sucht der Connector gezielt nach Matrix-Einträgen mit diesem Tag.
|
||
3. **Fallback:** Existiert für die gewählte Kampagne kein spezifischer Text für das Vertical/Persona, wird automatisch auf "standard" zurückgegriffen.
|
||
|
||
### 16. Email Sending Implementation (Feb 28, 2026)
|
||
|
||
A dedicated script `create_email_test.py` has been implemented to create "Email Documents" directly in SuperOffice via the API. This bypasses the need for an external SMTP server by utilizing SuperOffice's internal document system.
|
||
|
||
**Features:**
|
||
* **Document Creation:** Creates a document of type "Ausg. E-Mail" (Template ID 157).
|
||
* **Activity Tracking:** Automatically creates a linked "Appointment" (Task ID 6 - Document Out) to ensure the email appears in the contact's activity timeline.
|
||
* **Direct Link:** Outputs a direct URL to open the created document in SuperOffice Online.
|
||
|
||
**Usage:**
|
||
```bash
|
||
python3 connector-superoffice/create_email_test.py <PersonID>
|
||
# Example:
|
||
python3 connector-superoffice/create_email_test.py 193036
|
||
```
|
||
|
||
**Key API Endpoints Used:**
|
||
* `POST /Document`: Creates the email body and metadata.
|
||
* `POST /Appointment`: Creates the activity record linked to the document.
|
||
|
||
---
|
||
This is the core logic used to generate the company-specific opener.
|
||
|
||
**Goal:** Prove understanding of the business model + imply the pain (positive observation).
|
||
|
||
```text
|
||
Du bist ein exzellenter B2B-Stratege und Texter mit einem tiefen Verständnis für operative Prozesse.
|
||
Deine Aufgabe ist es, einen hochpersonalisierten, scharfsinnigen und wertschätzenden Einleitungssatz für eine E-Mail an ein potenzielles Kundenunternehmen zu formulieren.
|
||
|
||
--- Denkprozess & Stilvorgaben ---
|
||
1. **Analysiere den Kontext:** Verstehe das Kerngeschäft. Was ist die kritische, physische Tätigkeit vor Ort? (z.B. 'Betrieb von Hochregallagern', 'Pflege von Patienten').
|
||
2. **Identifiziere den Hebel:** Was ist der Erfolgsfaktor? (z.B. 'reibungslose Abläufe', 'maximale Hygiene').
|
||
3. **Formuliere den Satz (ca. 20-35 Wörter):**
|
||
- Wähle einen eleganten, aktiven Einstieg wie 'Speziell im Bereich...' oder 'Der reibungslose Betrieb...'.
|
||
- Verbinde die **spezifische Tätigkeit** mit dem **Hebel** und den **geschäftlichen Konsequenzen**.
|
||
- **WICHTIG:** Formuliere immer als positive Beobachtung über eine Kernkompetenz. Du implizierst die Herausforderung durch die Betonung der Wichtigkeit.
|
||
- **VERMEIDE:** Konkrete Zahlen (z.B. "35 Rutschen"), da diese veraltet sein können. Nutze abstrakte Größen ("weitläufige Anlagen").
|
||
``` |