docs: echte Umlaute in der Dokumentation
893 Stellen in Fliesstext, Tabellen und Mermaid-Beschriftungen. Dazu die sichtbaren ss-Fälle (ausschließlich, groß, außen, schließt, heißt, draußen, weiße, anstoßen, Straße). Nicht angetastet, damit die Doku weiter beschreibt, was wirklich in der Datenbank steht: - Codeblöcke und Code-Spans - dort stehen Bezeichner, SQL, Enum-Werte und Dateinamen: `mail_pool`, `'uebernommen'`, `extract.js` - in Mermaid nur der Text in Anführungszeichen; Knotennamen, Pfeile und Werte wie source = 'Mail' bleiben, wie sie sind - `empfaenger` im ER-Diagramm von crm_mail_contact_candidates: die Aufzählung dort nennt die Datenbankwerte, nicht die Anzeige Die Menü-Skizze in der Benutzeranleitung zeigt jetzt die aufgeklappten Kontakte mit "Aus Mails" samt Zähler. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8b7ce448a6
commit
7973dc07b1
+93
-93
@@ -1,15 +1,15 @@
|
||||
# 11 Postfachanbindung (Microsoft 365)
|
||||
|
||||
Das gemeinsame Vertriebspostfach `crm@perinco.de` wird **lesend** in das CRM
|
||||
gespiegelt. Aus jeder Mail entstehen Kontakt-Vorschlaege, und jede Mail laesst
|
||||
sich an eine Opportunity und an Kontakte haengen.
|
||||
gespiegelt. Aus jeder Mail entstehen Kontakt-Vorschläge, und jede Mail lässt
|
||||
sich an eine Opportunity und an Kontakte hängen.
|
||||
|
||||
Vorbild ist die gleiche Funktion im Altsystem KeepTrack. Uebernommen wurde die
|
||||
Vorbild ist die gleiche Funktion im Altsystem KeepTrack. Übernommen wurde die
|
||||
Idee, nicht die Umsetzung - die Unterschiede stehen unten unter
|
||||
[Was anders ist als im Altsystem](#was-anders-ist-als-im-altsystem) und sind der
|
||||
Grund fuer mehrere Entwurfsentscheidungen.
|
||||
Grund für mehrere Entwurfsentscheidungen.
|
||||
|
||||
## Ueberblick
|
||||
## Überblick
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -25,7 +25,7 @@ flowchart LR
|
||||
SY["mail/sync.js<br/>Delta Query"]
|
||||
EX["mail/extract.js<br/>Kontakte lesen"]
|
||||
ST["mail/store.js"]
|
||||
CO["mail/contacts.js<br/>Uebernahme"]
|
||||
CO["mail/contacts.js<br/>Übernahme"]
|
||||
API["routes/mail.js<br/>/api/mail"]
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ Login.
|
||||
2. **Client-Geheimnis** anlegen und den Wert sofort kopieren.
|
||||
3. **API-Berechtigung** Microsoft Graph → *Anwendungsberechtigungen* →
|
||||
`Mail.Read`, danach Administratorzustimmung erteilen.
|
||||
`Mail.ReadBasic` genuegt nicht, es liefert keinen Mailtext.
|
||||
`Mail.ReadBasic` genügt nicht, es liefert keinen Mailtext.
|
||||
4. **Zugriff auf dieses eine Postfach begrenzen.** Anwendungsberechtigungen
|
||||
gelten tenant-weit; ohne Richtlinie darf die App jedes Postfach lesen:
|
||||
|
||||
@@ -73,8 +73,8 @@ Login.
|
||||
Test-ApplicationAccessPolicy -Identity sascha.wolf@perinco.de -AppId <APP-ID> # Denied
|
||||
```
|
||||
|
||||
`Add-MailboxPermission` ist hier **wirkungslos** - das ist der Weg fuer
|
||||
delegierten Zugriff bzw. EWS-Impersonation. Bei App-only zaehlt allein die
|
||||
`Add-MailboxPermission` ist hier **wirkungslos** - das ist der Weg für
|
||||
delegierten Zugriff bzw. EWS-Impersonation. Bei App-only zählt allein die
|
||||
Zugriffsrichtlinie.
|
||||
|
||||
Der Server muss ausgehend `login.microsoftonline.com` und `graph.microsoft.com`
|
||||
@@ -82,8 +82,8 @@ auf 443 erreichen.
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
Zugangsdaten stehen ausschliesslich in `server/.env` - nie in der Datenbank und
|
||||
nie in einer API-Antwort. Ein Datenbank-Dump enthaelt damit keine Geheimnisse.
|
||||
Zugangsdaten stehen ausschließlich in `server/.env` - nie in der Datenbank und
|
||||
nie in einer API-Antwort. Ein Datenbank-Dump enthält damit keine Geheimnisse.
|
||||
|
||||
| Variable | Pflicht | Bedeutung |
|
||||
| --- | --- | --- |
|
||||
@@ -143,32 +143,32 @@ sequenceDiagram
|
||||
Wesentliche Eigenschaften:
|
||||
|
||||
* **Kein Schreibzugriff.** Der Fortschritt steht im `delta_link` in der
|
||||
Datenbank, nicht im `isRead`-Kennzeichen des Postfachs. Deshalb genuegt
|
||||
Datenbank, nicht im `isRead`-Kennzeichen des Postfachs. Deshalb genügt
|
||||
`Mail.Read`, und deshalb geht keine Mail verloren, wenn ein Mensch sie vorher
|
||||
in Outlook oeffnet.
|
||||
in Outlook öffnet.
|
||||
* **Fortsetzbar.** Nach jeder Seite wird der Verweis gesichert. Ein Absturz oder
|
||||
ein Deployment mitten im Erstabgleich kostet hoechstens eine Seite.
|
||||
ein Deployment mitten im Erstabgleich kostet höchstens eine Seite.
|
||||
* **Gedeckelt.** `MAIL_MAX_PAGES` begrenzt einen Lauf. Ist danach noch etwas
|
||||
offen, setzt der naechste Lauf nach 15 Sekunden fort (`has_more`), statt das
|
||||
offen, setzt der nächste Lauf nach 15 Sekunden fort (`has_more`), statt das
|
||||
normale Intervall abzuwarten.
|
||||
* **Selbstheilend.** Antwortet Graph mit `410 Gone` bzw. `resyncRequired`, wird
|
||||
der Verweis verworfen und beim naechsten Lauf neu aufgebaut. Vorhandene Mails
|
||||
der Verweis verworfen und beim nächsten Lauf neu aufgebaut. Vorhandene Mails
|
||||
werden am SHA-256 der Graph-ID wiedererkannt, es entstehen keine Doppelten.
|
||||
* **Nur Metadaten in der Delta-Abfrage.** Der Mailtext wird einzeln nachgeholt
|
||||
und nur, wenn er fehlt. Ein reines Metadaten-Update (etwa ein geaendertes
|
||||
Gelesen-Kennzeichen) loescht den gespeicherten Text nicht.
|
||||
und nur, wenn er fehlt. Ein reines Metadaten-Update (etwa ein geändertes
|
||||
Gelesen-Kennzeichen) löscht den gespeicherten Text nicht.
|
||||
* **Zeitgrenze doppelt.** Der Erstabgleich versucht
|
||||
`$filter=receivedDateTime ge ...`; die Filterunterstuetzung in Delta-Abfragen
|
||||
ist eng begrenzt, bei einem 400 laeuft er ohne Filter weiter. Der Filter ist
|
||||
deshalb nur eine Ersparnis - geprueft wird die Grenze **immer** beim Speichern,
|
||||
und zwar nur fuer noch unbekannte Mails. Ein an den ersten Lauf gebundener
|
||||
Merker waere hier falsch: ein ungefilterter Erstabgleich zieht sich ueber
|
||||
mehrere Laeufe, und in den folgenden kaeme der Rest des Postfachs trotz Grenze
|
||||
`$filter=receivedDateTime ge ...`; die Filterunterstützung in Delta-Abfragen
|
||||
ist eng begrenzt, bei einem 400 läuft er ohne Filter weiter. Der Filter ist
|
||||
deshalb nur eine Ersparnis - geprüft wird die Grenze **immer** beim Speichern,
|
||||
und zwar nur für noch unbekannte Mails. Ein an den ersten Lauf gebundener
|
||||
Merker wäre hier falsch: ein ungefilterter Erstabgleich zieht sich über
|
||||
mehrere Läufe, und in den folgenden käme der Rest des Postfachs trotz Grenze
|
||||
herein.
|
||||
|
||||
Im Postfach geloeschte Mails werden mit `is_deleted = 1` gekennzeichnet, nicht
|
||||
entfernt: eine an eine Opportunity gehaengte Mail soll nicht aus dem CRM
|
||||
verschwinden, weil jemand sein Postfach aufraeumt.
|
||||
Im Postfach gelöschte Mails werden mit `is_deleted = 1` gekennzeichnet, nicht
|
||||
entfernt: eine an eine Opportunity gehängte Mail soll nicht aus dem CRM
|
||||
verschwinden, weil jemand sein Postfach aufräumt.
|
||||
|
||||
## Kontakte aus Mails
|
||||
|
||||
@@ -178,7 +178,7 @@ nicht.
|
||||
```mermaid
|
||||
flowchart TD
|
||||
M["Mail"] --> A["Absender<br/>from (Graph)"]
|
||||
M --> E["Empfaenger<br/>to, cc, bcc (Graph)"]
|
||||
M --> E["Empfänger<br/>to, cc, bcc (Graph)"]
|
||||
M --> S["Signaturblock<br/>Text nach der Grussformel"]
|
||||
M --> T["Fliesstext<br/>Anrede + Name + Rufnummer"]
|
||||
|
||||
@@ -193,26 +193,26 @@ flowchart TD
|
||||
|
||||
C --> Z{"E-Mail vorhanden<br/>und Zuversicht ≥ 70?"}
|
||||
Z -->|ja, Automatik an| N["Eintrag in 'Aus Mails'<br/>mail_pool = 1"]
|
||||
Z -->|"Adresse dort schon"| G["Eintrag ergaenzen"]
|
||||
Z -->|"Adresse dort schon"| G["Eintrag ergänzen"]
|
||||
Z -->|nein| R["bleibt zur Durchsicht"]
|
||||
```
|
||||
|
||||
Zuversicht je Herkunft: Absender 90 (95, wenn die Signatur die Adresse
|
||||
bestaetigt), Empfaenger 78, weitere Adresse aus der Signatur 55, im Text genannte
|
||||
bestätigt), Empfänger 78, weitere Adresse aus der Signatur 55, im Text genannte
|
||||
Person 35.
|
||||
|
||||
Aus dem Signaturblock kommen Telefon und Mobil (nach Beschriftung
|
||||
unterschieden), Firma (an der Rechtsform erkannt), Position, Strasse, PLZ, Ort,
|
||||
unterschieden), Firma (an der Rechtsform erkannt), Position, Straße, PLZ, Ort,
|
||||
Website und LinkedIn-Profil.
|
||||
|
||||
Zwei Entscheidungen, die die Trefferqualitaet tragen:
|
||||
Zwei Entscheidungen, die die Trefferqualität tragen:
|
||||
|
||||
* **Im Fliesstext ist eine Anrede Pflicht** („Herrn Max Mustermann Tel. …").
|
||||
Ohne diese Bedingung liest der Musterabgleich jede Folge grossgeschriebener
|
||||
Woerter als Namen; „Zweiter Ansprechpartner" landete so als Kontakt. Ein
|
||||
Ohne diese Bedingung liest der Musterabgleich jede Folge großgeschriebener
|
||||
Wörter als Namen; „Zweiter Ansprechpartner" landete so als Kontakt. Ein
|
||||
verpasster Treffer ist harmloser als ein falscher - eine Durchsichtsliste voll
|
||||
Unsinn schaut niemand an.
|
||||
* **Der Anzeigename schlaegt die Adresse.** Bei „Mueller" plus
|
||||
* **Der Anzeigename schlägt die Adresse.** Bei „Müller" plus
|
||||
`k.mueller@…` wird der Vorname aus der Adresse genommen, der Nachname aber aus
|
||||
dem Anzeigenamen - dort stehen die Umlaute richtig.
|
||||
|
||||
@@ -221,64 +221,64 @@ Zwei Entscheidungen, die die Trefferqualitaet tragen:
|
||||
Der Hintergrundlauf schreibt **nicht** in den Kontaktbestand. Was er findet,
|
||||
landet in einem Vorbereich: `crm_leads.mail_pool = 1`. Die Kontaktansicht zeigt
|
||||
nur `mail_pool = 0`, der Vorbereich steht als Unterpunkt *Aus Mails* unter dem
|
||||
Menuepunkt *Kontakte*.
|
||||
Menüpunkt *Kontakte*.
|
||||
|
||||
Der Grund ist Pflegbarkeit: die Kontaktliste ist eine gepflegte Liste, an der
|
||||
Opportunities, Kalender und Netzwerk haengen. Ein Abgleich, der ungefragt jede
|
||||
Opportunities, Kalender und Netzwerk hängen. Ein Abgleich, der ungefragt jede
|
||||
Adresse aus jeder Mail hineinschreibt, macht sie unbrauchbar - genau daran ist
|
||||
das Altsystem gescheitert. Deshalb sind Funde aus Mails Rohmaterial, bis jemand
|
||||
entscheidet.
|
||||
|
||||
Eine Spalte statt einer zweiten Tabelle, weil `crm_mail_links` und
|
||||
`crm_mail_contact_candidates` per Fremdschluessel auf `crm_leads` zeigen: der
|
||||
Vorbereich behaelt damit seine Mailbezuege, ohne dass es zwei Wege gibt, eine
|
||||
`crm_mail_contact_candidates` per Fremdschlüssel auf `crm_leads` zeigen: der
|
||||
Vorbereich behält damit seine Mailbezüge, ohne dass es zwei Wege gibt, eine
|
||||
Person mit einer Mail zu verbinden.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P["Aus Mails<br/>mail_pool = 1"] --> D{"Person schon<br/>in den Kontakten?"}
|
||||
D -->|nein| NEU["In Kontakte uebernehmen<br/>Kopie mit mail_pool = 0"]
|
||||
D -->|nein| NEU["In Kontakte übernehmen<br/>Kopie mit mail_pool = 0"]
|
||||
D -->|"ja - Adresse oder Name"| GOLD["gold markiert<br/>Abweichungen hervorgehoben"]
|
||||
GOLD --> M["Felder einzeln in den<br/>vorhandenen Kontakt uebernehmen"]
|
||||
GOLD --> M["Felder einzeln in den<br/>vorhandenen Kontakt übernehmen"]
|
||||
GOLD --> N2["trotzdem neu anlegen"]
|
||||
P --> W["entfernen"]
|
||||
```
|
||||
|
||||
Der Abgleich gegen die Kontakte laeuft im Browser (`mailPool.ts`): erst ueber die
|
||||
E-Mail-Adresse, sonst ueber den vollen Namen. Telefonnummern werden dabei ueber
|
||||
Der Abgleich gegen die Kontakte läuft im Browser (`mailPool.ts`): erst über die
|
||||
E-Mail-Adresse, sonst über den vollen Namen. Telefonnummern werden dabei über
|
||||
ihre Ziffern verglichen, damit „0221 123-4" und „02211234" nicht als Abweichung
|
||||
erscheinen.
|
||||
|
||||
Beim Uebernehmen sind nur die **Luecken** vorausgewaehlt - Felder, in denen der
|
||||
Kontakt noch nichts hat. Werte, die einen gepflegten Eintrag ersetzen wuerden,
|
||||
sind als *ueberschreibt* gekennzeichnet und muessen bewusst angehakt werden: der
|
||||
Beim Übernehmen sind nur die **Lücken** vorausgewählt - Felder, in denen der
|
||||
Kontakt noch nichts hat. Werte, die einen gepflegten Eintrag ersetzen würden,
|
||||
sind als *überschreibt* gekennzeichnet und müssen bewusst angehakt werden: der
|
||||
Mailfund kann aus einer alten Signatur stammen.
|
||||
|
||||
Uebernommene Kontakte tragen weiterhin `crm_leads.source = 'Mail'` - die
|
||||
Kontaktliste hat dafuer den Herkunftsfilter *Aus Mails uebernommen*.
|
||||
Übernommene Kontakte tragen weiterhin `crm_leads.source = 'Mail'` - die
|
||||
Kontaktliste hat dafür den Herkunftsfilter *Aus Mails übernommen*.
|
||||
|
||||
Bestandsumstellung: `db.js` verschiebt beim ersten Start alle vorhandenen
|
||||
Kontakte mit `source = 'Mail'` in den Vorbereich. Das laeuft genau einmal,
|
||||
Kontakte mit `source = 'Mail'` in den Vorbereich. Das läuft genau einmal,
|
||||
festgehalten im Merker `leads_mail_pool_migrated` in `crm_settings` - sonst
|
||||
wanderten uebernommene Kontakte bei jedem Neustart zurueck.
|
||||
wanderten übernommene Kontakte bei jedem Neustart zurück.
|
||||
|
||||
## Anzeige des Mailtexts
|
||||
|
||||
Fremdes HTML laeuft in einem `<iframe sandbox="">` **ohne jedes allow-Flag**. Das
|
||||
Fremdes HTML läuft in einem `<iframe sandbox="">` **ohne jedes allow-Flag**. Das
|
||||
Dokument bekommt eine eigene, undurchsichtige Origin und kann weder Skripte
|
||||
ausfuehren noch auf die Anwendung zugreifen.
|
||||
ausführen noch auf die Anwendung zugreifen.
|
||||
|
||||
`allow-same-origin` waere hier ein Fehler: ein `srcDoc`-Dokument erbt damit die
|
||||
Origin der Anwendung und koennte `localStorage` lesen - also das Anmeldetoken.
|
||||
Weil die Hoehe des Inhalts ohne dieses Flag nicht auslesbar ist, hat der Rahmen
|
||||
eine feste Hoehe mit eigenem Rollbalken und einen Schalter „Groesser".
|
||||
`allow-same-origin` wäre hier ein Fehler: ein `srcDoc`-Dokument erbt damit die
|
||||
Origin der Anwendung und könnte `localStorage` lesen - also das Anmeldetoken.
|
||||
Weil die Höhe des Inhalts ohne dieses Flag nicht auslesbar ist, hat der Rahmen
|
||||
eine feste Höhe mit eigenem Rollbalken und einen Schalter „Größer".
|
||||
|
||||
Zusaetzlich entfernt das Backend beim Speichern Skripte, eingebettete Rahmen,
|
||||
Formulare, Ereignis-Attribute und gefaehrliche URL-Schemata
|
||||
Zusätzlich entfernt das Backend beim Speichern Skripte, eingebettete Rahmen,
|
||||
Formulare, Ereignis-Attribute und gefährliche URL-Schemata
|
||||
(`mail/html.js`, `sanitizeHtml`).
|
||||
|
||||
Externe Bilder sind per Content-Security-Policy im eingebetteten Dokument
|
||||
gesperrt und lassen sich pro Mail freigeben - andernfalls meldet jedes Oeffnen
|
||||
gesperrt und lassen sich pro Mail freigeben - andernfalls meldet jedes Öffnen
|
||||
dem Absender, dass die Mail gelesen wurde.
|
||||
|
||||
## Endpunkte
|
||||
@@ -287,79 +287,79 @@ Alle unter `/api/mail`, alle hinter `requireAuth`.
|
||||
|
||||
| Methode | Pfad | Rolle | Zweck |
|
||||
| --- | --- | --- | --- |
|
||||
| GET | `/status` | alle | Betriebszustand, Zaehler, letzter Fehler |
|
||||
| GET | `/status` | alle | Betriebszustand, Zähler, letzter Fehler |
|
||||
| GET | `/messages` | alle | Liste; `search`, `filter`, `limit`, `offset` |
|
||||
| GET | `/messages/:id` | alle | Mail mit Text, Zuordnungen, Vorschlaegen |
|
||||
| POST | `/messages/:id/links` | alle | an Kontakt und/oder Opportunity haengen |
|
||||
| DELETE | `/links/:id` | alle | Zuordnung loesen |
|
||||
| GET | `/candidates` | alle | Kontakt-Vorschlaege, `status` waehlbar |
|
||||
| POST | `/candidates/:id/adopt` | alle | Kontakt anlegen bzw. ergaenzen; `target: 'pool'` legt in „Aus Mails" ab |
|
||||
| GET | `/messages/:id` | alle | Mail mit Text, Zuordnungen, Vorschlägen |
|
||||
| POST | `/messages/:id/links` | alle | an Kontakt und/oder Opportunity hängen |
|
||||
| DELETE | `/links/:id` | alle | Zuordnung lösen |
|
||||
| GET | `/candidates` | alle | Kontakt-Vorschläge, `status` wählbar |
|
||||
| POST | `/candidates/:id/adopt` | alle | Kontakt anlegen bzw. ergänzen; `target: 'pool'` legt in „Aus Mails" ab |
|
||||
| POST | `/candidates/:id/reject` | alle | Vorschlag verwerfen |
|
||||
|
||||
Der Vorbereich haengt an den Kontakten und hat seine Endpunkte deshalb unter
|
||||
Der Vorbereich hängt an den Kontakten und hat seine Endpunkte deshalb unter
|
||||
`/api/leads`:
|
||||
|
||||
| Methode | Pfad | Zweck |
|
||||
| --- | --- | --- |
|
||||
| GET | `/leads/mail-pool/sources` | Herkunft je Eintrag: Mailanzahl, letzter Betreff, Zuversicht |
|
||||
| POST | `/leads/mail-pool/promote` | mehrere Eintraege als neue Kontakte anlegen |
|
||||
| POST | `/leads/:id/promote` | einen Eintrag uebernehmen; mit `target_lead_id` und `fields` in einen vorhandenen Kontakt |
|
||||
| POST | `/leads/mail-pool/promote` | mehrere Einträge als neue Kontakte anlegen |
|
||||
| POST | `/leads/:id/promote` | einen Eintrag übernehmen; mit `target_lead_id` und `fields` in einen vorhandenen Kontakt |
|
||||
|
||||
`keep_pool: true` behaelt den Eintrag in „Aus Mails"; ohne das verschwindet er
|
||||
dort, sobald er als Kontakt gefuehrt wird. In beiden Faellen wandern die
|
||||
Mailverknuepfungen mit an den Zielkontakt.
|
||||
`keep_pool: true` behält den Eintrag in „Aus Mails"; ohne das verschwindet er
|
||||
dort, sobald er als Kontakt geführt wird. In beiden Fällen wandern die
|
||||
Mailverknüpfungen mit an den Zielkontakt.
|
||||
| GET | `/settings` | admin | verstellbare Einstellungen |
|
||||
| POST | `/settings` | admin | Einstellungen speichern |
|
||||
| POST | `/test` | admin | Anmeldung **und** Postfachzugriff pruefen |
|
||||
| POST | `/sync` | admin | Abgleich anstossen, `{"reset":true}` von vorn |
|
||||
| POST | `/test` | admin | Anmeldung **und** Postfachzugriff prüfen |
|
||||
| POST | `/sync` | admin | Abgleich anstoßen, `{"reset":true}` von vorn |
|
||||
|
||||
Lesen ist fuer jeden Angemeldeten frei - `crm@perinco.de` ist ein gemeinsames
|
||||
Lesen ist für jeden Angemeldeten frei - `crm@perinco.de` ist ein gemeinsames
|
||||
Vertriebspostfach. Konfigurieren, Testen und Anstossen sind Administratoren
|
||||
vorbehalten.
|
||||
|
||||
`filter` in der Liste: `unlinked` (noch nirgends zugeordnet - die Arbeitsliste),
|
||||
`candidates` (offene Kontakt-Vorschlaege), `linked` (bereits zugeordnet).
|
||||
`candidates` (offene Kontakt-Vorschläge), `linked` (bereits zugeordnet).
|
||||
|
||||
## Was anders ist als im Altsystem
|
||||
|
||||
| Thema | KeepTrack | perinco CRM |
|
||||
| --- | --- | --- |
|
||||
| Fortschritt | `$filter=isRead eq false`, danach `PATCH isRead` | Delta Query mit gespeichertem `delta_link` |
|
||||
| Berechtigung | Schreibzugriff noetig | `Mail.Read` genuegt |
|
||||
| Der Haken daran | `PATCH` ging auf `/me/…` - unter App-only gibt es kein `/me`. Der Aufruf lief in einen 400, die Antwort wurde nicht geprueft. Mails blieben ungelesen und wurden bei **jedem** Lauf erneut durch das Sprachmodell geschickt. | entfaellt |
|
||||
| Berechtigung | Schreibzugriff nötig | `Mail.Read` genügt |
|
||||
| Der Haken daran | `PATCH` ging auf `/me/…` - unter App-only gibt es kein `/me`. Der Aufruf lief in einen 400, die Antwort wurde nicht geprüft. Mails blieben ungelesen und wurden bei **jedem** Lauf erneut durch das Sprachmodell geschickt. | entfällt |
|
||||
| Tenant-/Client-ID | kamen vom Browser mit; jeder Angemeldete konnte das Server-Secret gegen einen fremden Tenant verwenden | serverseitig aus der `.env`, nie im Browser |
|
||||
| Kontakte ohne Adresse | Platzhalter `name@kontakt-aus-email.local` erfunden, um eine Datenbankbedingung zu erfuellen | Kontakt ohne Adresse; Vorschlag bleibt zur Durchsicht liegen |
|
||||
| Vorhandener Kontakt | wurde uebersprungen | leere Felder werden ergaenzt, gepflegte bleiben |
|
||||
| Kontaktdaten lesen | kompletter Mailtext an OpenAI, auch fuer Daten, die Graph strukturiert liefert | Graph-Felder plus Signaturauswertung, kein externer Dienst |
|
||||
| Mail am Deal | Text in das Freitextfeld `notizen` vorangestellt | eigene Tabelle `crm_mail_links`, loesbar, mehrfach moeglich |
|
||||
| Verbindungstest | prueft nur, ob ein Token kommt | prueft Token **und** Postfachzugriff, mit Hinweis je Fehlercode |
|
||||
| Kontakte ohne Adresse | Platzhalter `name@kontakt-aus-email.local` erfunden, um eine Datenbankbedingung zu erfüllen | Kontakt ohne Adresse; Vorschlag bleibt zur Durchsicht liegen |
|
||||
| Vorhandener Kontakt | wurde übersprungen | leere Felder werden ergänzt, gepflegte bleiben |
|
||||
| Kontaktdaten lesen | kompletter Mailtext an OpenAI, auch für Daten, die Graph strukturiert liefert | Graph-Felder plus Signaturauswertung, kein externer Dienst |
|
||||
| Mail am Deal | Text in das Freitextfeld `notizen` vorangestellt | eigene Tabelle `crm_mail_links`, lösbar, mehrfach möglich |
|
||||
| Verbindungstest | prüft nur, ob ein Token kommt | prüft Token **und** Postfachzugriff, mit Hinweis je Fehlercode |
|
||||
| Paging | `@odata.nextLink` ignoriert, `$top=50` fest | Seiten werden gefolgt, je Lauf gedeckelt, fortsetzbar |
|
||||
| HTML-Anzeige | ungefiltert | serverseitig entschaerft, `sandbox=""`, externe Bilder gesperrt |
|
||||
| HTML-Anzeige | ungefiltert | serverseitig entschärft, `sandbox=""`, externe Bilder gesperrt |
|
||||
| Zeitangaben | gemischt | durchgehend UTC in der Datenbank, Umrechnung im Frontend |
|
||||
|
||||
## Was noch nicht drin ist
|
||||
|
||||
* **Senden, Antworten, Weiterleiten.** Braucht `Mail.ReadWrite` und `Mail.Send`
|
||||
und damit eine erneute Administratorzustimmung.
|
||||
* **Anhaenge.** `has_attachments` wird gefuehrt und angezeigt, die Dateien
|
||||
* **Anhänge.** `has_attachments` wird geführt und angezeigt, die Dateien
|
||||
selbst werden nicht geholt.
|
||||
* **Automatische Zuordnung zu Opportunities.** Die Zuordnung erfolgt per Hand;
|
||||
der Dialog schlaegt anhand der Mailadressen Kontakt und Opportunity vor. Eine
|
||||
Bewertung durch ein Sprachmodell waere der naechste Schritt - dann bitte mit
|
||||
der Dialog schlägt anhand der Mailadressen Kontakt und Opportunity vor. Eine
|
||||
Bewertung durch ein Sprachmodell wäre der nächste Schritt - dann bitte mit
|
||||
dem Schutz gegen Prompt-Injection, der im Altsystem in `mail-sync` fehlte.
|
||||
* **Echtzeit statt Intervall.** Graph Change Notifications brauchen einen
|
||||
oeffentlich erreichbaren Endpunkt (haben wir) und eine Erneuerung der
|
||||
öffentlich erreichbaren Endpunkt (haben wir) und eine Erneuerung der
|
||||
Subscription etwa alle drei Tage.
|
||||
|
||||
## Stoerungssuche
|
||||
## Störungssuche
|
||||
|
||||
| Beobachtung | Ursache und Abhilfe |
|
||||
| --- | --- |
|
||||
| Einstellungen melden fehlende Variablen | `server/.env` ergaenzen, Dienst neu starten - die Werte werden beim Start gelesen |
|
||||
| Einstellungen melden fehlende Variablen | `server/.env` ergänzen, Dienst neu starten - die Werte werden beim Start gelesen |
|
||||
| Test scheitert „bei der Anmeldung" | Tenant-ID, Client-ID oder Geheimnis falsch, oder das Geheimnis ist abgelaufen |
|
||||
| Test scheitert „beim Postfachzugriff", `ErrorAccessDenied` | Administratorzustimmung fehlt, oder die ApplicationAccessPolicy erlaubt dieses Postfach nicht |
|
||||
| Test scheitert mit `ResourceNotFound` | `MS_MAILBOX` zeigt auf ein Postfach, das es nicht gibt |
|
||||
| „Erstabgleich laeuft noch" bleibt stehen | Log auf `[Mail]` pruefen; `MAIL_MAX_PAGES` erhoehen, wenn es zu langsam vorangeht |
|
||||
| Keine Kontakt-Vorschlaege | stehen alle Absender auf einer eigenen Domain? `mail_internal_domains` pruefen |
|
||||
| Doppelte Kontakte | tritt nicht auf: Abgleich erfolgt ueber die Adresse in Kleinschreibung; bei Treffer wird ergaenzt statt angelegt |
|
||||
| Zaehler und Liste weichen ab | die Liste blendet im Postfach geloeschte Mails aus, der Zaehler in den Einstellungen ebenfalls - Zuordnungen bleiben trotzdem erhalten |
|
||||
| „Erstabgleich läuft noch" bleibt stehen | Log auf `[Mail]` prüfen; `MAIL_MAX_PAGES` erhöhen, wenn es zu langsam vorangeht |
|
||||
| Keine Kontakt-Vorschläge | stehen alle Absender auf einer eigenen Domain? `mail_internal_domains` prüfen |
|
||||
| Doppelte Kontakte | tritt nicht auf: Abgleich erfolgt über die Adresse in Kleinschreibung; bei Treffer wird ergänzt statt angelegt |
|
||||
| Zähler und Liste weichen ab | die Liste blendet im Postfach gelöschte Mails aus, der Zähler in den Einstellungen ebenfalls - Zuordnungen bleiben trotzdem erhalten |
|
||||
|
||||
Reference in New Issue
Block a user