feat(#299): Sender→Label Memory — Domain-Fallback #307

Merged
admin-mrrm merged 1 commit from feat/299-sender-memory-domain-fallback into main 2026-05-16 13:25:44 +02:00
Owner

Summary

  • SenderMemoryService.lookup fällt bei fehlendem Exact-Match auf eine Aggregat-Query pro Domain zurück (split_part(sender_addr, '@', 2), SUM(confirmCount), SUM(removeCount), COUNT(DISTINCT senderAddr)).
  • Provider-Domain-Blacklist (gmail, outlook, gmx, web.de, icloud, proton, t-online, …) schließt Multi-Tenant-Domains vom Fallback aus → keine false positives zwischen verschiedenen Nutzern derselben Mail-Provider.
  • Domain-Match-Schwelle bewusst strenger als Exact (sum-confirm ≥ 3, ≥ 2 verschiedene Adressen, confirm > remove), weil Domain-Aggregate weniger spezifisch sind.
  • SenderMemoryEntry.matchType: 'exact' | 'domain' exponiert die Herkunft für UI/Logging.

API-Contract

senderMemoryTagSchema erhält matchType: z.enum(['exact', 'domain']).default('exact') — alte Server, die das Feld nicht senden, bleiben kompatibel.

Die bestehenden Categorizer-Threshold-Checks (confirmCount ≥ 2 AND confirm > remove) gelten für Domain-Treffer weiter; die strengeren Server-seitigen Filter laufen vorher und garantieren, dass Domain-Aggregate nur mit echter Evidenz kommen.

Test plan

  • 26 neue Unit-Tests (extractDomain, isProviderDomain, alle Lookup-Pfade inkl. Threshold-Edges + Provider-Blacklist)
  • pnpm vitest run apps/api: 271/271 grün
  • pnpm typecheck -r: clean (alle 11 Packages)
  • pnpm test -r: apps/web 57/57, apps/mobile 49/49 — kein Bruch durch das neue optionale matchType
  • Smoke-Test auf dev-neu: Setup mehrere DHL-Adressen mit Tag „Sendung", dann unbekannte DHL-Adresse → Tag wird per Domain vergeben.

Bezug

Follow-up zu #294 (Sender→Label Memory). Komplementär zu #298 (Auto-Decay, PR #306). Kein Schema-Change, keine Migration nötig — reine Live-Aggregation auf der bestehenden mail_sender_label_memory-Tabelle.

Closes #299

## Summary - `SenderMemoryService.lookup` fällt bei fehlendem Exact-Match auf eine Aggregat-Query pro Domain zurück (`split_part(sender_addr, '@', 2)`, `SUM(confirmCount)`, `SUM(removeCount)`, `COUNT(DISTINCT senderAddr)`). - Provider-Domain-Blacklist (`gmail`, `outlook`, `gmx`, `web.de`, `icloud`, `proton`, `t-online`, …) schließt Multi-Tenant-Domains vom Fallback aus → keine false positives zwischen verschiedenen Nutzern derselben Mail-Provider. - Domain-Match-Schwelle bewusst strenger als Exact (sum-confirm ≥ 3, ≥ 2 verschiedene Adressen, confirm > remove), weil Domain-Aggregate weniger spezifisch sind. - `SenderMemoryEntry.matchType: 'exact' | 'domain'` exponiert die Herkunft für UI/Logging. ## API-Contract `senderMemoryTagSchema` erhält `matchType: z.enum(['exact', 'domain']).default('exact')` — alte Server, die das Feld nicht senden, bleiben kompatibel. Die bestehenden Categorizer-Threshold-Checks (`confirmCount ≥ 2 AND confirm > remove`) gelten für Domain-Treffer weiter; die strengeren Server-seitigen Filter laufen vorher und garantieren, dass Domain-Aggregate nur mit echter Evidenz kommen. ## Test plan - [x] 26 neue Unit-Tests (`extractDomain`, `isProviderDomain`, alle Lookup-Pfade inkl. Threshold-Edges + Provider-Blacklist) - [x] `pnpm vitest run` apps/api: 271/271 grün - [x] `pnpm typecheck -r`: clean (alle 11 Packages) - [x] `pnpm test -r`: apps/web 57/57, apps/mobile 49/49 — kein Bruch durch das neue optionale `matchType` - [ ] Smoke-Test auf dev-neu: Setup mehrere DHL-Adressen mit Tag „Sendung", dann unbekannte DHL-Adresse → Tag wird per Domain vergeben. ## Bezug Follow-up zu #294 (Sender→Label Memory). Komplementär zu #298 (Auto-Decay, PR #306). Kein Schema-Change, keine Migration nötig — reine Live-Aggregation auf der bestehenden `mail_sender_label_memory`-Tabelle. Closes #299
feat(#299): Sender→Label Memory — Domain-Fallback
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
67809ed96e
Bei fehlendem Exact-Match auf `senderAddr` zusätzliche Aggregat-Query
nach Domain (`split_part(sender_addr, '@', 2)`), gefiltert auf:

- distinctSenders ≥ 2 (mind. 2 verschiedene Adressen derselben Domain)
- sum(confirmCount) ≥ 3
- sum(confirmCount) > sum(removeCount)

Damit löst `info@dhl.de` und `service@dhl.de` denselben Tag aus, sobald
genug exakte Bestätigungen aus der DHL-Domain vorliegen.

- Provider-Domain-Blacklist (gmail/outlook/gmx/web.de/icloud/proton/...)
  schließt Multi-Tenant-Domains aus, um false positives zu vermeiden.
- `SenderMemoryEntry.matchType: 'exact' | 'domain'` exponiert die
  Match-Herkunft; api-client zod-Schema mit `.default('exact')` für
  Backwards-Compat mit alten Servern.
- Schwelle ist strenger als Exact-Match (`confirmCount ≥ 2`), weil
  Domain-Aggregate inhärent weniger spezifisch sind.

Closes #299
admin-mrrm deleted branch feat/299-sender-memory-domain-fallback 2026-05-16 13:25:44 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
admin-mrrm/mrrmlabapp!307
No description provided.