137 lines
4.2 KiB
Markdown
137 lines
4.2 KiB
Markdown
# 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
|