[2ff88f42] Implementierung E-Mail-Dokument-Automatisierung und technischer Check der Versand-Blocker. Workaround via SuperOffice-Aktivitäten etabliert.
Implementierung E-Mail-Dokument-Automatisierung und technischer Check der Versand-Blocker. Workaround via SuperOffice-Aktivitäten etabliert.
This commit is contained in:
@@ -1 +1 @@
|
||||
{"task_id": "31088f42-8544-8017-96da-fa75bb6d8121", "token": "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", "readme_path": null, "session_start_time": "2026-02-27T15:09:49.228277"}
|
||||
{"task_id": "2ff88f42-8544-8000-8314-c9013414d1d0", "token": "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", "readme_path": "connector-superoffice/README.md", "session_start_time": "2026-02-28T14:25:23.133066"}
|
||||
12
GEMINI.md
12
GEMINI.md
@@ -156,11 +156,13 @@ Since the "Golden Record" for Industry Verticals (Pains, Gains, Products) reside
|
||||
- **Purpose:** Lists all property keys and page titles. Use this to debug schema changes (e.g. if a column was renamed).
|
||||
- **Usage:** `python3 list_notion_structure.py`
|
||||
|
||||
## Next Steps
|
||||
* **Marketing Automation:** Implement the actual sending logic (or export) based on the contact status.
|
||||
* **Job Role Mapping Engine:** Connect the configured patterns to the contact import/creation process to auto-assign roles.
|
||||
* **Industry Classification Engine:** Connect the configured industries to the AI Analysis prompt to enforce the "Strict Mode" mapping.
|
||||
* **Export:** Generate Excel/CSV enriched reports (already partially implemented via JSON export).
|
||||
## Next Steps (Updated Feb 27, 2026)
|
||||
* **Notion Content:** Finalize "Pains" and "Gains" for all 25 verticals in the Notion master database.
|
||||
* **Intelligence:** Run `generate_matrix.py` in the Company Explorer backend to populate the matrix for all new English vertical names.
|
||||
* **Automation:** Register the production webhook (requires `admin-webhooks` rights) to enable real-time CRM sync without manual job injection.
|
||||
* **Execution:** Connect the "Sending Engine" (the actual email dispatch logic) to the SuperOffice fields.
|
||||
* **Monitoring:** Monitor the 'Atomic PATCH' logs in production for any 400 errors regarding field length or specific character sets.
|
||||
|
||||
|
||||
## Company Explorer Access & Debugging
|
||||
|
||||
|
||||
@@ -412,7 +412,17 @@ Der Company Explorer unterstützt nun den Parameter `campaign_tag`. Der Connecto
|
||||
|
||||
---
|
||||
|
||||
## 18. Next Steps & Todos (Post-Migration)
|
||||
* **Task 1:** Monitoring & Alerting (Dashboard Ausbau).
|
||||
* **Task 2:** Robust Address Parsing (Google Maps API).
|
||||
* **Task 3:** "Person-First" Logic (Reverse Lookup via Domain).
|
||||
## 18. Offene Arbeitspakete (Stand: 27.02.2026)
|
||||
|
||||
### Prio A: Operative Automatisierung
|
||||
* **Webhook-Aktivierung:** Registrierung des Live-Webhooks für `online3`, sobald Admin-Rechte für den API-User vorliegen (`register_webhook.py`).
|
||||
* **Full Matrix Generation:** Ausführung der KI-Generierung für alle 25 Verticals (englische IDs), sobald die "Pains" in Notion finalisiert wurden.
|
||||
* **Campaign-Validation:** Erstellung von Test-Szenarien für mindestens 3 verschiedene Kampagnen-Tags zur Verifizierung der Weichenstellung.
|
||||
|
||||
### Prio B: Marketing-Execution
|
||||
* **Sending Logic:** Implementierung der Logik für den tatsächlichen E-Mail-Versand (oder Export zu einem E-Mail-Provider) basierend auf den befüllten UDFs.
|
||||
* **Unsubscribe-Frontend:** Visuelle Gestaltung der HTML-Bestätigungsseite für den Unsubscribe-Link.
|
||||
|
||||
### Prio C: Daten-Optimierung
|
||||
* **Google Maps API:** Einbindung zur Validierung von Firmenadressen bei Diskrepanzen zwischen CRM und Scraper.
|
||||
* **Deduplication 2.0:** Verfeinerung des Matchings bei Firmen mit mehreren Standorten (Filial-Logik).
|
||||
|
||||
38
connector-superoffice/attempt_agent_send.py
Normal file
38
connector-superoffice/attempt_agent_send.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(override=True)
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
|
||||
def attempt_send(to_email: str):
|
||||
client = SuperOfficeClient()
|
||||
|
||||
# Payload for Agents/EMail/Send
|
||||
# It expects an array of "EMail" objects
|
||||
payload = [
|
||||
{
|
||||
"To": [{"Value": to_email, "Address": to_email}],
|
||||
"Subject": "Test from SuperOffice Agent API",
|
||||
"HTMLBody": "<h1>Hello!</h1><p>This is a test from the Agents/EMail/Send endpoint.</p>",
|
||||
"From": {"Value": "system@roboplanet.de", "Address": "system@roboplanet.de"} # Try to force a sender
|
||||
}
|
||||
]
|
||||
|
||||
print(f"🚀 Attempting POST /Agents/EMail/Send to {to_email}...")
|
||||
try:
|
||||
# Note: The endpoint might be v1/Agents/EMail/Send
|
||||
res = client._post("Agents/EMail/Send", payload)
|
||||
if res:
|
||||
print("✅ Success! Response:", json.dumps(res, indent=2))
|
||||
else:
|
||||
print("❌ Request failed (None returned).")
|
||||
except Exception as e:
|
||||
print(f"❌ Exception during send: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
attempt_send("floke.com@gmail.com")
|
||||
36
connector-superoffice/check_crmscript.py
Normal file
36
connector-superoffice/check_crmscript.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(override=True)
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
|
||||
def check_crmscript():
|
||||
client = SuperOfficeClient()
|
||||
|
||||
print(f"🚀 Checking CRMScript capability...")
|
||||
|
||||
# 1. Check if we can list scripts
|
||||
try:
|
||||
# Agents/CRMScript/GetCRMScripts
|
||||
res = client._post("Agents/CRMScript/GetCRMScripts", payload={"CRMScriptIds": []}) # Empty array usually gets all or error
|
||||
if res:
|
||||
print(f"✅ Can access CRMScripts. Response type: {type(res)}")
|
||||
else:
|
||||
# Try GET Archive
|
||||
print("⚠️ Agent access failed/empty. Trying Archive...")
|
||||
res = client._get("Archive/dynamic?$select=all&$top=1&entity=crmscript")
|
||||
if res:
|
||||
print(f"✅ CRMScript Entity found in Archive.")
|
||||
else:
|
||||
print(f"❌ CRMScript Entity NOT found in Archive.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Exception checking CRMScript: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_crmscript()
|
||||
71
connector-superoffice/create_mailing_test.py
Normal file
71
connector-superoffice/create_mailing_test.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(override=True)
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("mailing-test")
|
||||
|
||||
def create_mailing(person_id: int):
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 1. Get Person & Marketing Texts
|
||||
person = client._get(f"Person/{person_id}")
|
||||
if not person:
|
||||
print(f"❌ Person {person_id} not found.")
|
||||
return
|
||||
|
||||
email_address = person.get("Emails", [{}])[0].get("Value")
|
||||
if not email_address:
|
||||
print(f"❌ Person {person_id} has no email address.")
|
||||
return
|
||||
|
||||
udefs = person.get("UserDefinedFields", {})
|
||||
subject = udefs.get(settings.UDF_SUBJECT)
|
||||
intro = udefs.get(settings.UDF_INTRO)
|
||||
proof = udefs.get(settings.UDF_SOCIAL_PROOF)
|
||||
|
||||
if not all([subject, intro, proof]):
|
||||
print("❌ Marketing texts missing in Person UDFs. Run provisioning first.")
|
||||
return
|
||||
|
||||
full_body = f"{intro}\n\n{proof}\n\nAbmelden: {udefs.get(settings.UDF_UNSUBSCRIBE_LINK)}"
|
||||
|
||||
# 2. Create a "Shipment" (Individual Mailing)
|
||||
# Based on SO Documentation for Marketing API
|
||||
# We try to create a Shipment directly.
|
||||
|
||||
payload = {
|
||||
"Name": f"Gemini Outreach: {subject}",
|
||||
"Subject": subject,
|
||||
"Body": full_body,
|
||||
"DocumentTemplateId": 157, # Ausg. E-Mail
|
||||
"ShipmentType": "Email",
|
||||
"AssociateId": 528, # RCGO
|
||||
"ContactId": person.get("Contact", {}).get("ContactId"),
|
||||
"PersonId": person_id,
|
||||
"Status": "Ready" # This might trigger the internal SO send process
|
||||
}
|
||||
|
||||
print(f"📤 Creating Shipment for {email_address}...")
|
||||
try:
|
||||
# Endpoints to try: /Shipment or /Mailing
|
||||
# Let's try /Shipment
|
||||
resp = client._post("Shipment", payload)
|
||||
if resp:
|
||||
print(f"✅ Shipment created successfully! ID: {resp.get('ShipmentId')}")
|
||||
print(json.dumps(resp, indent=2))
|
||||
else:
|
||||
print("❌ Shipment creation returned no data.")
|
||||
except Exception as e:
|
||||
print(f"❌ Shipment API failed: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_mailing(193036)
|
||||
87
connector-superoffice/diagnose_email_capability.py
Normal file
87
connector-superoffice/diagnose_email_capability.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(override=True)
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("diagnose-email")
|
||||
|
||||
def diagnose():
|
||||
print("🔍 Starting Email Capability Diagnosis...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 1. Check Licenses / Capabilities via Associate/Me
|
||||
print("\n--- 1. User & License Check ---")
|
||||
try:
|
||||
me = client._get("Associate/Me")
|
||||
if me:
|
||||
print(f"User: {me.get('Name')} (ID: {me.get('AssociateId')})")
|
||||
print(f"Type: {me.get('Type')}")
|
||||
# Check for specific functional rights if available in the object
|
||||
# (Note: API often hides raw license keys, but let's check what we get)
|
||||
print("Function Rights (TableRight):", me.get("TableRight"))
|
||||
else:
|
||||
print("❌ Could not fetch current user.")
|
||||
except Exception as e:
|
||||
print(f"❌ User check failed: {e}")
|
||||
|
||||
# 2. Check User Preferences (Email Settings)
|
||||
# Endpoint: GET /Preference/{section}/{key}
|
||||
# We look for 'Mail' section preferences
|
||||
print("\n--- 2. Email Preferences (System & User) ---")
|
||||
pref_keys = [
|
||||
("Mail", "EmailClient"),
|
||||
("Mail", "EmailSystem"),
|
||||
("System", "SoProtocol"),
|
||||
("Visual", "UseWebTools")
|
||||
]
|
||||
|
||||
for section, key in pref_keys:
|
||||
try:
|
||||
# Note: The API for preferences might be /Preference/<Section>/<Key>
|
||||
# or require a search. Let's try direct access first.
|
||||
res = client._get(f"Preference/{section}/{key}")
|
||||
if res:
|
||||
print(f"✅ Preference '{section}/{key}': {json.dumps(res, indent=2)}")
|
||||
else:
|
||||
print(f"❓ Preference '{section}/{key}' not found or empty.")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error checking preference '{section}/{key}': {e}")
|
||||
|
||||
# 3. Check for specific functional rights (Archive/List)
|
||||
# If we can access 'ShipmentType' list, we might have Marketing
|
||||
print("\n--- 3. Marketing Capability Check ---")
|
||||
try:
|
||||
shipment_types = client._get("List/ShipmentType/Items")
|
||||
if shipment_types:
|
||||
print(f"✅ Found {len(shipment_types)} Shipment Types (Marketing module likely active).")
|
||||
for st in shipment_types:
|
||||
print(f" - {st.get('Name')} (ID: {st.get('Id')})")
|
||||
else:
|
||||
print("❌ No Shipment Types found (Marketing module might be inactive/restricted).")
|
||||
except Exception as e:
|
||||
print(f"❌ Error checking Shipment Types: {e}")
|
||||
|
||||
# 4. Check Document Template for 'Email'
|
||||
print("\n--- 4. Document Template Configuration ---")
|
||||
try:
|
||||
# We know ID 157 exists, let's inspect it closely
|
||||
tmpl = client._get("DocumentTemplate/157")
|
||||
if tmpl:
|
||||
print(f"Template 157: {tmpl.get('Name')}")
|
||||
print(f" - Generator: {tmpl.get('Generator')}") # Important!
|
||||
print(f" - Filename: {tmpl.get('Filename')}")
|
||||
print(f" - Direction: {tmpl.get('Direction')}")
|
||||
else:
|
||||
print("❌ Template 157 not found via ID.")
|
||||
except Exception as e:
|
||||
print(f"❌ Error checking Template 157: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
diagnose()
|
||||
@@ -1,62 +1,50 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(override=True)
|
||||
from superoffice_client import SuperOfficeClient
|
||||
import logging
|
||||
|
||||
# Setup Logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("discovery")
|
||||
# Force unbuffered stdout
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
|
||||
def discover():
|
||||
print("🔍 Starting SuperOffice Discovery Tool...")
|
||||
print("🔍 Starting SuperOffice Discovery Tool (Direct Sending)...")
|
||||
|
||||
client = SuperOfficeClient()
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to init client: {e}")
|
||||
return
|
||||
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed. Check .env")
|
||||
return
|
||||
|
||||
# 1. Discover UDFs (User Defined Fields)
|
||||
print("\n--- 1. User Defined Fields (UDFs) Definitions ---")
|
||||
# 4. Check Sending Endpoints
|
||||
print("\n--- 4. Direct Sending Endpoints ---")
|
||||
|
||||
# EMail Agent
|
||||
print(f"Checking Endpoint: Agents/EMail/GetDefaultEMailFromAddress...")
|
||||
try:
|
||||
# Fetch Metadata about UDFs to get Labels
|
||||
udf_info = client._get("UserDefinedFieldInfo")
|
||||
if udf_info:
|
||||
print(f"Found {len(udf_info)} UDF definitions.")
|
||||
|
||||
# Filter for Contact and Person UDFs
|
||||
contact_udfs = [u for u in udf_info if u['UDTargetEntityName'] == 'Contact']
|
||||
person_udfs = [u for u in udf_info if u['UDTargetEntityName'] == 'Person']
|
||||
|
||||
print(f"\n--- CONTACT UDFs ({len(contact_udfs)}) ---")
|
||||
for u in contact_udfs:
|
||||
print(f" - Label: '{u['FieldLabel']}' | ProgId: '{u['ProgId']}' | Type: {u['UDFieldType']}")
|
||||
|
||||
print(f"\n--- PERSON UDFs ({len(person_udfs)}) ---")
|
||||
for u in person_udfs:
|
||||
print(f" - Label: '{u['FieldLabel']}' | ProgId: '{u['ProgId']}' | Type: {u['UDFieldType']}")
|
||||
|
||||
res = client._get("Agents/EMail/GetDefaultEMailFromAddress")
|
||||
if res:
|
||||
print(f"✅ Agents/EMail active. Default From: {json.dumps(res)}")
|
||||
else:
|
||||
print("❌ Could not fetch UserDefinedFieldInfo.")
|
||||
|
||||
print(f"❓ Agents/EMail returned None (likely 404/403).")
|
||||
except Exception as e:
|
||||
print(f"❌ Error fetching UDF Info: {e}")
|
||||
print(f"❌ Agents/EMail check failed: {e}")
|
||||
|
||||
print("\n--- 2. Sample Data Inspection ---")
|
||||
|
||||
lists_to_check = ["position", "business"]
|
||||
|
||||
for list_name in lists_to_check:
|
||||
print(f"\nChecking List: '{list_name}'...")
|
||||
try:
|
||||
# Endpoint: GET /List/{list_name}/Items
|
||||
items = client._get(f"List/{list_name}/Items")
|
||||
if items:
|
||||
print(f"Found {len(items)} items in '{list_name}':")
|
||||
for item in items:
|
||||
print(f" - ID: {item['Id']} | Name: '{item['Name']}'")
|
||||
else:
|
||||
print(f" (List '{list_name}' is empty or not accessible)")
|
||||
except Exception as e:
|
||||
print(f" ❌ Failed to fetch list '{list_name}': {e}")
|
||||
# TicketMessage
|
||||
print(f"Checking Endpoint: Archive/dynamic (Ticket)...")
|
||||
try:
|
||||
res = client._get("Archive/dynamic?$select=all&$top=1&entity=ticket")
|
||||
if res:
|
||||
print(f"✅ Ticket entities found. Service module active.")
|
||||
else:
|
||||
print(f"❓ No Ticket entities found (Service module inactive?).")
|
||||
except Exception as e:
|
||||
print(f"❌ Ticket check failed: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
discover()
|
||||
|
||||
20
connector-superoffice/inspect_person_full.py
Normal file
20
connector-superoffice/inspect_person_full.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(override=True)
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def inspect_person(person_id: int):
|
||||
client = SuperOfficeClient()
|
||||
print(f"📡 Fetching FULL Person {person_id}...")
|
||||
person = client._get(f"Person/{person_id}")
|
||||
if person:
|
||||
print(json.dumps(person, indent=2))
|
||||
else:
|
||||
print("❌ Person not found.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
inspect_person(193036)
|
||||
@@ -99,8 +99,10 @@ class SuperOfficeClient:
|
||||
logger.warning(f"⚠️ 401 Unauthorized for {endpoint}. Attempting Token Refresh...")
|
||||
new_token = self._refresh_access_token()
|
||||
if new_token:
|
||||
logger.info("✅ Token refreshed successfully during retry.")
|
||||
self.access_token = new_token
|
||||
self.headers["Authorization"] = f"Bearer {self.access_token}"
|
||||
# Recursive retry with the new token
|
||||
return self._request_with_retry(method, endpoint, payload, retry=False)
|
||||
else:
|
||||
logger.error("❌ Token Refresh failed during retry.")
|
||||
|
||||
Reference in New Issue
Block a user