4.9 KiB
Design: FamilyGuard Flyer-Download mit E-Mail-Gate
Datum: 2026-07-21 Status: Approved
Ziel
Der FamilyGuard-Flyer (docs/MBO_FamilyGuard_Flyer_01.pdf) soll auf der Website herunterladbar sein, aber erst nachdem der Interessent seine E-Mail-Adresse eingegeben und der Datenverarbeitung zugestimmt hat. Jonny (jonny@mbo-tech-it.de) soll bei jedem Download per Mail benachrichtigt werden. Die eingegebenen Adressen werden zusätzlich als Lead in Supabase gespeichert.
Architektur
modules/08-familyguard-flyer/
migrations/
MIGRATIONS_FLYER_DOWNLOADS.sql ← neue Tabelle flyer_downloads
app/api/familyguard-flyer/
route.ts ← POST: Lead speichern, 2 Mails senden, Download-URL zurückgeben
components/
FlyerDownloadForm.tsx ← Client-Komponente: E-Mail + DSGVO-Checkbox + Submit
public/downloads/
MBO_FamilyGuard_Flyer_01.pdf ← verschoben aus docs/
FlyerDownloadForm wird nur einmal eingebunden, auf app/pakete/familyguard/page.tsx als neue Sektion (id="flyer-download") vor dem Bottom-Callout. Um das Formular nicht doppelt pflegen zu müssen, bekommt components/FamilyGuardPromo.tsx auf der Startseite stattdessen einen Button „Flyer herunterladen", der per Anchor-Link zu /pakete/familyguard#flyer-download springt. Das erfüllt „FamilyGuard-Seite + Homepage-Promo-Kachel", ohne die Formular-Logik zweimal zu implementieren.
Datenmodell
Neue Tabelle flyer_downloads (Migration nach dem Muster der bestehenden modules/*/migrations/*.sql):
CREATE TABLE IF NOT EXISTS flyer_downloads (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
email TEXT NOT NULL,
flyer TEXT NOT NULL DEFAULT 'familyguard',
dsgvo_einwilligung BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_flyer_downloads_email ON flyer_downloads(email);
CREATE INDEX IF NOT EXISTS idx_flyer_downloads_created_at ON flyer_downloads(created_at DESC);
ALTER TABLE flyer_downloads DISABLE ROW LEVEL SECURITY;
flyer als Textfeld (statt hartcodiert) hält die Tabelle offen für künftige weitere Flyer, ohne dass das jetzt genutzt wird (YAGNI: nur ein Default-Wert, keine weitere Logik dafür).
Da im lokalen .env.local nur Platzhalter-Credentials für Supabase liegen, kann die Migration nicht von hier aus ausgeführt werden — Jonny führt das SQL manuell in seiner Supabase-Instanz aus (wie bei den bestehenden Modulen).
API-Route POST /api/familyguard-flyer
Request: { email: string, dsgvoEinwilligung: boolean }
Ablauf (analog zu app/api/contact/route.ts):
- Validierung:
emailvorhanden + Format,dsgvoEinwilligung === true→ sonst 400 - Insert in
flyer_downloadsviacreateServiceClient()(Fehler werden geloggt, blockieren aber nicht den Download) sendeFlyerBenachrichtigung({ email })→ anprocess.env.SMTP_TO-Äquivalent, konkret hartcodiert anjonny@mbo-tech-it.de(kein Konfigurationsfeld nötig, da fest gewünscht)sendeFlyerLink({ email })→ an den Interessenten, mit Link zum PDF (${APP_URL}/downloads/MBO_FamilyGuard_Flyer_01.pdf)- Response:
{ ok: true, downloadUrl: "/downloads/MBO_FamilyGuard_Flyer_01.pdf" }
Beide Mail-Funktionen kommen als neue Exporte in lib/mailer.ts, nutzen den bestehenden sendWithFallback-Helper (SMTP mit Queue-Fallback), gleiches HTML-Layout wie die bestehenden Mails.
Komponente FlyerDownloadForm.tsx
Client-Komponente nach dem Muster von Contact.tsx:
- State:
email,dsgvoChecked,status(idle | loading | success | error) - Submit →
fetch("/api/familyguard-flyer", { method: "POST", ... }) - Bei Erfolg: Formular wird durch einen Download-Button (
<a href={downloadUrl} download>) ersetzt - Checkbox-Label verlinkt auf
/datenschutz - Bei Fehler: Fehlermeldung wie im bestehenden Kontaktformular
Fehlerbehandlung
- SMTP nicht erreichbar → automatischer Fallback in die bestehende
email_queue(kein Nutzer-Impact) - Supabase-Insert schlägt fehl → wird geloggt, Download funktioniert trotzdem
- Ungültige/leere E-Mail oder fehlende Einwilligung → 400 mit Fehlermeldung, Formular zeigt Inline-Fehler
- PDF-Datei ist eine normale statische Datei unter
/public/downloads— kein Access-Control-Layer, das Gate ist rein UX-seitig (bewusste Entscheidung, siehe Architektur-Diskussion: für einen Marketing-Flyer ist ein hartes Access-Gating unverhältnismäßig)
Testing
Manueller Test über npm run dev:
- Formular auf
/pakete/familyguardausfüllen (mit und ohne Checkbox) → Validierung prüfen - Nach Erfolg: Download-Button erscheint, PDF öffnet sich korrekt
- Eintrag in
flyer_downloadsprüfen (sofern echte Supabase-Zugangsdaten vorhanden sind) - Beide Mails prüfen (Empfang bei SMTP_TO/jonny@mbo-tech-it.de und bei der Test-E-Mail-Adresse) bzw. Queue-Eintrag bei fehlendem SMTP
- Homepage-Kachel: Link führt korrekt zur Formular-Sektion auf der FamilyGuard-Seite