diff --git a/connector-superoffice/README.md b/connector-superoffice/README.md index 81684ac3..65e900d1 100644 --- a/connector-superoffice/README.md +++ b/connector-superoffice/README.md @@ -253,32 +253,27 @@ Das System unterstützt mehrere Outreach-Varianten über das Feld **`MA_Campaign 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. -### 15. Advanced Implementation Details (v1.8) +### 16. Email Sending Implementation (Feb 28, 2026) -Mit der Version 1.8 des Workers wurden kritische Optimierungen für den produktiven Betrieb (online3) implementiert, um API-Stabilität und Datenintegrität zu gewährleisten. +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. -#### A. Atomic PATCH Strategy -Um "Race Conditions" und unnötigen API-Traffic zu vermeiden, bündelt der Worker alle Änderungen an einem Kontakt-Objekt in einem einzigen **Atomic PATCH**. -* **Betroffene Felder:** `Address` (Postal & Street), `OrgNr` (VAT), `Urls` (Website) und alle `UserDefinedFields`. -* **Vorteil:** Entweder alle Daten werden konsistent übernommen, oder der Call schlägt kontrolliert fehl. Dies verhindert, dass Teil-Updates (z.B. nur die Adresse) von nachfolgenden UDF-Updates überschrieben werden. +**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. -#### B. REST Website-Sync (The `Urls` Array) -SuperOffice REST akzeptiert kein direktes Update auf `UrlAddress` via PATCH. Stattdessen muss das `Urls` Array manipuliert werden. -* **Logik:** Der Worker prüft, ob die KI-entdeckte Website bereits im Array vorhanden ist. Wenn nicht, wird sie als neues Objekt mit der Beschreibung `"AI Discovered"` an den Anfang der Liste gestellt. -* **Format:** `"Urls": [{"Value": "https://...", "Description": "AI Discovered"}]`. +**Usage:** +```bash +python3 connector-superoffice/create_email_test.py +# Example: +python3 connector-superoffice/create_email_test.py 193036 +``` -#### C. Kampagnen-Auflösung via `:DisplayText` -Um den Klarnamen einer Kampagne (z.B. "Messe 2026") statt der internen ID (z.B. `[I:123]`) zu erhalten, nutzt der Worker eine OData-Optimierung. -* **Technik:** Im `$select` Parameter wird das Feld `SuperOffice:23:DisplayText` angefordert. -* **Ergebnis:** Der Worker erhält direkt den sauberen String, der zur Steuerung der Textvarianten im Company Explorer dient. Zusätzliche API-Abfragen zur Listenauflösung entfallen. - -#### D. Feldlängen & Truncation -Standard-UDF-Textfelder in SuperOffice sind oft auf **254 Zeichen** begrenzt. Da das AI-Dossier (Summary) deutlich länger sein kann, kürzt der Worker den Text hart auf **132 Zeichen** (+ "..."). Dies stellt sicher, dass der gesamte `PATCH` Request nicht aufgrund eines "Field Overflow" von der SuperOffice-Validierung abgelehnt wird. +**Key API Endpoints Used:** +* `POST /Document`: Creates the email body and metadata. +* `POST /Appointment`: Creates the activity record linked to the document. --- - - -## Appendix: The "First Sentence" Prompt This is the core logic used to generate the company-specific opener. **Goal:** Prove understanding of the business model + imply the pain (positive observation). diff --git a/connector-superoffice/create_email_test.py b/connector-superoffice/create_email_test.py new file mode 100644 index 00000000..05c40cfa --- /dev/null +++ b/connector-superoffice/create_email_test.py @@ -0,0 +1,131 @@ +import os +import requests +import json +import logging +import argparse +from dotenv import load_dotenv +load_dotenv(override=True) +from superoffice_client import SuperOfficeClient +from config import settings + +# Setup Logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("create-email-test") + +def create_email_document(person_id_input: int): + print(f"🚀 Creating Email Document for Person ID {person_id_input}...") + + client = SuperOfficeClient() + if not client.access_token: + print("❌ Auth failed. Check .env") + return + + # --- TARGET PERSON --- + target_person_id = person_id_input + contact_id = None + person_id = None + + print(f"📡 Fetching target Person {target_person_id}...") + try: + person = client._get(f"Person/{target_person_id}") + if not person: + print(f"❌ Person {target_person_id} not found.") + return + + print(f"✅ Found Person: {person.get('Firstname')} {person.get('Lastname')}") + + # Get associated Contact ID + contact_id = person.get('Contact', {}).get('ContactId') + if not contact_id: + print("❌ Person has no associated company (ContactId).") + return + + # Verify Contact + contact = client._get(f"Contact/{contact_id}") + if contact: + print(f"✅ Associated Company: {contact.get('Name')} (ID: {contact_id})") + + person_id = target_person_id + + except Exception as e: + print(f"❌ Error fetching Person/Contact: {e}") + return + + if not contact_id or not person_id: + print("❌ Could not resolve Contact/Person IDs.") + return + + # 2. Define Email Content + subject = "Test E-Mail from Gemini CLI (API)" + body = f"""Hallo {person.get('Firstname')}, + +Dies ist ein Test für die Erstellung eines E-Mail-Dokuments direkt über die SuperOffice API. +Wir nutzen das Template 'Ausg. E-Mail' (ID 157). + +Viele Grüße, +Gemini""" + + # 3. Create Document Payload + # Note: DocumentTemplateId 157 = "Ausg. E-Mail" + template_id = 157 + + payload = { + "Name": subject, # Internal Name + "Header": subject, # Subject Line + # "OurRef": {"AssociateId": my_associate_id}, # Omitted, hoping SO uses API User context + "Contact": {"ContactId": contact_id}, + "Person": {"PersonId": person_id}, + "DocumentTemplate": {"DocumentTemplateId": template_id}, + "Content": body + } + + print(f"📤 Sending POST /Document payload...") + try: + doc = client._post("Document", payload) + if doc: + doc_id = doc.get('DocumentId') + print(f"✅ Document Created Successfully!") + print(f" ID: {doc_id}") + print(f" Name: {doc.get('Name')}") + print(f" Template: {doc.get('DocumentTemplate', {}).get('Name')}") + + # Construct direct link + # Format: https://online3.superoffice.com/Cust26720/default.aspx?document_id=334050 + env = settings.SO_ENVIRONMENT + cust_id = settings.SO_CONTEXT_IDENTIFIER + # Note: This is a best-guess link format for SO Online + link = f"https://{env}.superoffice.com/{cust_id}/default.aspx?document_id={doc_id}" + print(f"🔗 Direct Link: {link}") + + # 4. Create Linked Appointment (Activity) + print("📅 Creating Linked Appointment (Email Sent Activity)...") + appt_payload = { + "Description": body, + "Contact": {"ContactId": contact_id}, + "Person": {"PersonId": person_id}, + "Task": {"Id": 6}, # 6 = Document / Email Out + "Document": {"DocumentId": doc_id}, + "MainHeader": f"E-Mail: {subject}"[:40] + } + try: + appt = client._post("Appointment", appt_payload) + if appt: + print(f"✅ Appointment Created: {appt.get('AppointmentId')}") + else: + print("⚠️ Failed to create appointment (None response).") + except Exception as e: + print(f"⚠️ Failed to create appointment: {e}") + + else: + print("❌ Failed to create document (Response was empty/None).") + + except Exception as e: + print(f"❌ Error creating document: {e}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Create a test email document in SuperOffice.') + parser.add_argument('person_id', type=int, help='The SuperOffice Person ID to attach the email to.') + + args = parser.parse_args() + + create_email_document(args.person_id) \ No newline at end of file