4.2 KiB
4.2 KiB
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)
-- 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ösunganalytics: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, AktionenCampaignEditor— HTML + Plain-Text TabsRecipientPicker— Toggle: Liste ODER SegmentSchedulePicker— Sofort / Datum+Uhrzeit / Cron-AusdruckCampaignAnalytics— 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