Marketing email lives in the platform's own database; Mailjet is only the delivery layer behind the EmailDistributor abstraction (ACS failover, Null in dev/test). Tenant admins get a read-only view in their own admin app.
Lists
List
Scope
Active
Pending
Unsubscribed
Bounced / complained / suppressed
Product news (platform)
platform
18 402
312
1 204
88 / 12 / 41
Competition digest
platform
9 811
145
640
35 / 4 / 12
Scoreboard releases
platform
2 145
58
90
9 / 1 / 3
Nordvik federation news
tenant: Svenska Bouleförbundet
6 320
77
210
18 / 2 / 6
Double opt-in tokens: HMAC-SHA256-signed, purpose-bound (confirm 14 d · prefs 30 d · unsub 90 d). Public endpoints: subscribe (Turnstile + honeypot + 5/IP/h), confirm, preferences, unsubscribe, unsubscribe-all, RFC 8058 one-click, open-pixel and click-redirect tracking.
Subscriber actions: GDPR erase (removes PII, keeps suppression hash), move status pending→active, add to suppression list. CSV import requires an attested consent proof (source, method, timestamp) before any row is accepted.
Subscriber model — growth fields (D-GROWTH-0 Slice 0):role (enum: player · club_official · federation) and country (ISO 3166-1) are first-class subscriber fields, alongside UTM attribution utm_source / utm_medium / utm_campaign. All are captured at signup on the public waitlist/newsletter form and stored on the subscriber — they drive the country-by-country waves and segmentation, and the attribution reporting.
language eq "sv"source in [webinar_registration, signup_form]role eq "club_official"country in [SE, FR]engagement_score gte 0.4
OR
GROUP · ANY of (OR) — tenant_role eq "club_admin" · + drag a condition here
Allow-listed fields (incl. role, country, source, language) & ops only (eq, neq, in, nin, gt/gte/lt/lte, escaped regex, exists) — no raw queries.
MJML editor — "August product drop"
<mj-section><mj-text>
Hi {{first_name}} — here is what shipped in August…
{% content-block cta variants=[club, federation, player] %}
</mj-text></mj-section>
Rendered HTML is cached per template version. Dynamic content blocks are evaluated per subscriber at send time (language / tenant / merge_data).
Other triggers: tenant_onboarded, manual_enroll. Webinar registration offers a GDPR-strict, default-unticked newsletter opt-in that enrols as pending (source=webinar_registration).
Deliverability — per from-domain
Domain
Reputation
SPF / DKIM / DMARC
Bounce 30d
Complaint 30d
news.petanque.life
Good (94/100)
pass / pass / pass
0.6 %
0.02 %
—
mail.petanque.life
Watch (71/100)
pass / pass / p=quarantine
1.8 %
0.08 %
Auto-halt thresholds: bounce > 2 % or complaint > 0.1 %. Spam-score history per campaign kept alongside.
Suppression rules: hard bounce → suppress immediately; 5 consecutive soft bounces → escalate to suppression; complaint → suppress + status=complained. Suppression list is checked before every send.
AI assist — Gemini 2.5 Pro
✨ Subject lines — 10 suggestions ranked by predicted open rate📝 Content polish — tone/length rewrite of a draft block🎭 Audience reaction preview — 5 personas react before you send
Quota: 37 / 50 requests left (per operator, 24 h). Every call is audit-logged with prompt + response hash.
Engagement analytics
🔥 Click heatmap — click-coordinate aggregation per campaign📊 Cohort open/click rates — by tenant, role, language, source📉 Engagement score — rolling 30 d per subscriber; score < 0.1 flags at-risk and feeds the re-engagement automation
Event data (opens/clicks) is kept 90 days (TTL), aggregates forever.
Compliance: consent proof stored per subscribe (IP hash + UA hash + form id + timestamp + method) · CAN-SPAM physical address auto-injected in every footer · full audit trail (subscribe/confirm/preferences/unsubscribe/import/suppression/GDPR) · 3-year post-unsubscribe retention, then erase.
Tenant visibility: tenant admins see a read-only view of their own scope (lists, subscriber counts, campaigns) in their admin app — scoped by tenant_id, 403 without tenant context.
Webinar management
Webinars
Slug
Title
Starts
Attendees
Status
Note
getting-started-draw-engine
Getting started with the draw engine
2026-08-14 19:00
96 / 150
SCHEDULED
promoted (4 w before)
club-site-in-30-min
A club site in 30 minutes
2026-08-28 18:30
41 / 100
SCHEDULED
promoted
scoreboard-kits-in-practice
Scoreboard kits in practice
2026-06-11 19:00
138 attended
PUBLISHED
recording attached
licensing-deep-dive
Licensing deep dive
—
—
DRAFT
never leaks to public API
Status machine: DRAFT → SCHEDULED → LIVE → ENDED → PUBLISHED (or CANCELLED). Attendees are unique per (webinar, email); interest leads unique per email with topics, unsubscribed and notified counters.
Edit webinar — getting-started-draw-engine
Email flow (ACS): confirmation, reminder-24h, reminder-1h, recording-available, cancelled — with Google Calendar & Outlook deep links, EN/SV with EN fallback. Reminder job every 15 min (±15 min tolerance); post-event job hourly fans out to attendees + matching interest leads. Both idempotent via sent-at markers.
Google integration status:connected — service account with domain-wide delegation. Error states surfaced here: not_configured · client_library_missing · calendar_api_error (retry with backoff).
Empty — no webinars yet: "Create your first webinar; DRAFTs never leak to the public API."
Loading attendee list…
Error — calendar_api_error while syncing: the webinar saved, but the Meet event was not updated. Retry sync.
Esc
↵ opens the first hit · Esc closes · 🔒 = locked function (still shown — with an explanation)