chore: CLAUDE.md, PROMPT.md und Planungs-Docs hinzufügen

This commit is contained in:
2026-04-17 08:17:18 +00:00
parent 689fbac795
commit 7c7fc23b8c
4 changed files with 2516 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
# Kampagnen-Funktion — Design
**Datum:** 2026-04-17
---
## Ziel
Newsletter-SaaS-Plattform mit vollständiger Kampagnen-Funktion: einmaliger Versand, zeitgesteuerter Versand und event-basierte Automations — alle mit Tenant-Isolation, Suppression-Check und ClickHouse-Analytics.
---
## Entscheidungen
| Thema | Entscheidung |
|---|---|
| Kampagnen-Typen | Newsletter + Automation (beide gleichzeitig) |
| Empfänger | Feste Liste ODER dynamisches Segment wählbar |
| Inhaltsformate | HTML + Plain-Text (beide manuell pflegbar) |
| Status-Workflow | `draft → scheduled → sending → sent` |
| Versand-Zeitpunkt | Sofort, einmalig geplant, oder Cron-wiederkehrend |
| Analytics | Opens, Clicks, Bounces, Unsubscribes (aggregiert) |
| Automation-Trigger | Cron UND Event-Trigger kombinierbar |
---
## Datenmodell (PostgreSQL, schema-per-tenant)
```sql
-- Kampagne
campaigns (
id UUID PK,
name TEXT NOT NULL,
subject TEXT NOT NULL,
html_body TEXT NOT NULL,
plain_body TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
-- draft | scheduled | sending | sent | paused | cancelled
scheduled_at TIMESTAMPTZ,
cron_expression TEXT, -- NULL = einmalig
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
-- Empfänger-Zuordnung (Liste ODER Segment — genau eines davon NOT NULL)
campaign_recipients (
id UUID PK,
campaign_id UUID NOT NULL REFERENCES campaigns(id),
list_id UUID NULL, -- feste Subscriber-Liste
segment_id UUID NULL -- dynamisches Segment
)
-- Trigger für Automations
campaign_triggers (
id UUID PK,
campaign_id UUID NOT NULL REFERENCES campaigns(id),
trigger_type TEXT NOT NULL, -- cron | event
trigger_value TEXT NOT NULL -- Cron-Ausdruck oder Event-Name (z.B. "subscriber.optin")
)
```
**ClickHouse** (`email_events`, append-only):
- Felder: `event_type`, `tenant_id`, `campaign_id`, `recipient_hash` (SHA256), `timestamp`
- Kein Klartext-E-Mail in ClickHouse
---
## API Routes (Next.js App Router)
```
POST /api/campaigns → Kampagne erstellen (Status: draft)
GET /api/campaigns/:id → Kampagne abrufen
PATCH /api/campaigns/:id → Inhalt / Status aktualisieren
POST /api/campaigns/:id/send → Sofortversand auslösen
POST /api/campaigns/:id/schedule → Zeitplan oder Cron setzen
GET /api/campaigns/:id/analytics → Aggregierte ClickHouse-Daten
```
---
## Queue-Architektur (BullMQ)
**Versand-Flow:**
```
API → withTenant() → Suppression-Check
→ BullMQ Job enqueuen (email:send)
→ Job: Empfänger auflösen (Liste oder Segment)
→ Pro Empfänger: Suppression-Check → SMTP
→ ClickHouse INSERT (email_events)
```
**Queues:**
- `email:send` — Versand-Jobs (maxAttempts: 3, exponentielles Backoff, DLQ)
- `campaign:trigger` — Event-basierte Automation-Auslösung
- `analytics:scheduler` — Cron-Kampagnen-Prüfung (scheduled_at + cron_expression)
**Status-Übergänge:** Atomar in PostgreSQL mit Optimistic Locking — kein Doppelversand.
---
## Frontend (Next.js App Router)
**Seiten:**
```
/campaigns → Übersicht (CampaignCard-Liste, Status-Badge)
/campaigns/new → Wizard: Name → Inhalt → Empfänger → Zeitplan → Review
/campaigns/:id → Detail-Ansicht
/campaigns/:id/edit → Bearbeiten (nur im draft-Status)
/campaigns/:id/analytics → Aggregierte Analytics-Ansicht
```
**Komponenten:**
- `CampaignCard` — Status-Badge, Kurzinfo, Aktionen
- `CampaignEditor` — HTML + Plain-Text Tabs
- `RecipientPicker` — Toggle: Liste ODER Segment
- `SchedulePicker` — Sofort / Datum+Uhrzeit / Cron-Ausdruck
- `CampaignAnalytics` — Zahlen aus ClickHouse via API
**State:** Server Components + React Server Actions — kein globaler Client-Store.
---
## Deliverability-Pflichten
- Suppression-Check ist PFLICHT vor jedem Versand
- List-Unsubscribe-Header (RFC 8058) in jeder E-Mail
- Bounce-Rate > 5% → Kampagne automatisch pausieren
- Kein Klartext-Logging von E-Mail-Adressen
---
## DSGVO
- Double-Opt-In Pflicht (Consent-Timestamp + IP + User-Agent)
- Subscriber-Löschung löscht auch ClickHouse-Events (via tenant_id)
- Alle Daten auf Hetzner DE

File diff suppressed because it is too large Load Diff