chore: CLAUDE.md, PROMPT.md und Planungs-Docs hinzufügen
This commit is contained in:
136
docs/plans/2026-04-17-kampagnen-design.md
Normal file
136
docs/plans/2026-04-17-kampagnen-design.md
Normal 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
|
||||
Reference in New Issue
Block a user