# 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`): ```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`): 1. Validierung: `email` vorhanden + Format, `dsgvoEinwilligung === true` → sonst 400 2. Insert in `flyer_downloads` via `createServiceClient()` (Fehler werden geloggt, blockieren aber nicht den Download) 3. `sendeFlyerBenachrichtigung({ email })` → an `process.env.SMTP_TO`-Äquivalent, konkret hartcodiert an `jonny@mbo-tech-it.de` (kein Konfigurationsfeld nötig, da fest gewünscht) 4. `sendeFlyerLink({ email })` → an den Interessenten, mit Link zum PDF (`${APP_URL}/downloads/MBO_FamilyGuard_Flyer_01.pdf`) 5. 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 (``) 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`: 1. Formular auf `/pakete/familyguard` ausfüllen (mit und ohne Checkbox) → Validierung prüfen 2. Nach Erfolg: Download-Button erscheint, PDF öffnet sich korrekt 3. Eintrag in `flyer_downloads` prüfen (sofern echte Supabase-Zugangsdaten vorhanden sind) 4. 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 5. Homepage-Kachel: Link führt korrekt zur Formular-Sektion auf der FamilyGuard-Seite