Skip to main content
Petanque Life

Webinar Management

F19.16 11 features Shipped

At a glance

Webinar management runs the full marketing-webinar lifecycle on petanque.life — Google Meet event creation, public hub and detail pages in four languages, double-opt-in registration with Turnstile and rate-limit, an emerald-strip homepage banner, automated 24h and 1h reminders, recording publishing and a topic-interest waitlist for empty-state nurture — turning live demos into a repeatable acquisition channel.

How it works

Webinars are owned end-to-end inside the platform rather than glued together from Calendly + Zoom + Mailchimp. A MarketingWebinar document carries the slug, multilingual title and description, start time, duration, host, speakers, capacity, the auto-generated Google Meet URL, recording and slides links, status (DRAFT → SCHEDULED → LIVE → ENDED → PUBLISHED, plus CANCELLED) and homepage promotion flags. MarketingWebinarAttendee tracks registrations on a (webinar_id, email) compound index, while MarketingWebinarInterest is a topic-based waitlist (unique email + topic array, language preference, unsubscribe state).

Public endpoints serve everything visitors and the www site need: GET /public/webinars (upcoming, 5-minute cache), /past (only published with recording URL), /next (powers the homepage banner, returns 204 when empty), /{slug} (detail). Drafts and cancelled events never leak. POST /public/webinars/{slug}/register is idempotent on email, gated by Turnstile and per-IP rate-limit (10/24h), returns 409 when full or closed, and triggers a confirmation email via ACS.

A separate /public/webinar-interest endpoint upserts topic interest, treats honeypot fills as silent success, and re-activates unsubscribed users on return. On the www side, the dynamic /webinar hub renders an upcoming-grid, an empty-state with a 6-topic interest form, and a past recordings grid. The /webinar/{slug} page is SSG-built per slug and either shows a registration form (when SCHEDULED/LIVE without recording) or a 'Watch recording' CTA.

The WebinarBanner.astro component runs across all four locale homepages, fetches /next, and is dismissable per visitor with localStorage keyed on the webinar id — so a new promoted webinar reappears automatically. Google Workspace Calendar + Meet integration uses a service account with domain-wide delegation. Five inline ACS email templates cover confirmation, 24h and 1h reminders, recording-available and cancelled, with Google Calendar + Outlook deep-links and EN/SV with EN fallback.

Two batch jobs — webinar_reminder_dispatch and webinar_post_event_dispatch — run on cadence, idempotent via reminder_*_sent_at and post_event_sent_at flags.

Key capabilities

  • MarketingWebinar model with status machine, multilingual fields and homepage promotion flags
  • Google Workspace Calendar + Meet via service account with domain-wide delegation
  • Public hub /webinar and dynamic detail /webinar/{slug} in EN/FR/ES/SV
  • Idempotent registration with Turnstile, rate-limit and ACS confirmation email
  • Topic-interest waitlist for empty-state nurture and post-event fan-out
  • Homepage WebinarBanner with /next fetch, 204-silent and localStorage-dismiss
  • Reminder + post-event dispatch jobs, idempotent and rate-tolerant

In practice

A federation president sees the emerald banner on petanque.life: 'Federation onboarding webinar — Tuesday 14:00 CEST, 30 minutes.' She clicks, lands on /webinar/federation-onboarding, reads the agenda and host bio in French, registers with her work email. The Turnstile passes silently and she gets a confirmation email with Google Calendar and Outlook deep-links — one click and it's on her calendar. 24 hours before, the reminder dispatcher sends a polite nudge with the Meet URL. One hour before, the second reminder lands.

She attends. Two days later the recording-available email arrives with the slides. Three weeks later a recording-only webinar on a previously expressed interest topic fans out to her interest record automatically.

Features in this subsystem

11
ID Status Features
F19.16.01 Shipped MarketingWebinar-modell: slug (unique index), title_i18n, description_md_i18n, starts_at, duration_minutes (15–180), host_name, host_email, speakers[], max_attendees, meet_url, calendar_event_id, recording_url, slides_url, status (DRAFT/SCHEDULED/LIVE/ENDED/PUBLISHED/CANCELLED), promote_on_homepage, promote_weeks_before (1–12, default 4), tags[] ✅ PL-T151
F19.16.02 Shipped MarketingWebinarAttendee + MarketingWebinarInterest — attendee med compound-index (webinar_id, email), interest med unique-index på email och topics[], unsubscribed, notified_count, last_notified_at ✅ PL-T151
F19.16.03 Shipped Publika endpoints: GET /public/webinars (upcoming, cache 5 min), GET /public/webinars/past (published + recording_url), GET /public/webinars/next (banner, 204 när tom), GET /public/webinars/{slug} (detalj) — leakar aldrig DRAFT/CANCELLED ✅ PL-T151
F19.16.04 Shipped POST /public/webinars/{slug}/register — idempotent på email, Turnstile + per-IP rate-limit (10/24h), 409 webinar_full/webinar_not_open_for_registration, skickar bekräftelsemail via ACS ✅ PL-T151
F19.16.05 Shipped POST /public/webinar-interest — upsert på email, merge:ar topics + språk, honeypot website_url → silent success, rate-limit 20/IP/24h, flippar tillbaka unsubscribed=false på återkomst ✅ PL-T151
F19.16.06 Shipped Dynamisk www-hubsida /webinar (+ sv/fr/es-locales): hero, upcoming-grid (md:3-kol), empty-state med interest-formulär (6 topics), past recording-grid, 4-språks i18n, window.__plAnalytics-spårning ✅ PL-T151
F19.16.07 Shipped Dynamisk detaljsida /webinar/{slug}: SSG via getStaticPaths mot /public/webinars, hero + 2-kols-body + sidebar (host/duration/tags), registreringsformulär när SCHEDULED/LIVE utan inspelning, "Watch recording"-CTA när recording_url satt ✅ PL-T151
F19.16.08 Shipped Homepage WebinarBanner.astro — emerald-700-strip överst på alla 4 locale-homepages, fetchar /public/webinars/next, 204 → silent, localStorage-dismiss på pl.webinar_banner_dismissed_id återvisas automatiskt när nytt webinar promoteras ✅ PL-T151
F19.16.09 Shipped Google Workspace Calendar + Meet via Service Account + domain-wide delegation: create_meet_event/update_meet_event/cancel_meet_event, sendUpdates="all" på uppdatering/cancel, error-koder not_configured/client_library_missing/calendar_api_error ✅ PL-T151
F19.16.10 Shipped ACS-mailflöde via webinar_email.py: 5 inline-format-string-mallar (confirmation, reminder-24h, reminder-1h, recording-available, cancelled), Google Calendar + Outlook deep-links, en/sv med fallback till en ✅ PL-T151
F19.16.11 Shipped Batch-jobb webinar_reminder_dispatch (15 min, ±15 min tolerans, 24h + 1h-fönster) och webinar_post_event_dispatch (hourly: fan-out till deltagare + matchande interest-leads efter recording-attach) — idempotenta via reminder_*_sent_at / post_event_sent_at ✅ PL-T151