Newsletter Engine (super-premium, multi-list, multi-tenant)
I korthet
The newsletter engine is a fully owned, super-premium marketing-email platform — multi-list, multi-tenant, MJML templates, visual segment builder, drip automations, A/B testing with statistical winner selection, per-recipient send-time optimisation, Gemini-powered AI assist, GDPR-strict double opt-in and Litmus cross-client previews — replacing Mailchimp, ConvertKit and Customer.io without losing a single feature.
Så fungerar det
Petanque Life owns its email channel rather than renting it. An EmailDistributor abstraction (Mailjet primary, ACS failover, Null in dev) handles deliverability, while every other concern — subscribers, lists, campaigns, opens, clicks, segments, preference centre, automations, suppressions — lives in the platform's own MongoDB. Lists are scoped platform-wide or per tenant.
Subscribers move through pending → active → unsubscribed/bounced/complained/suppressed. Templates are MJML source plus a rendered HTML cache. Campaigns follow draft → scheduled → sending → sent.
Events live for 90 days under a TTL index. Double opt-in uses HMAC-SHA256 signed, purpose-bound tokens (confirm/prefs/unsub, TTL 14/30/90 days). Public endpoints cover subscribe (Turnstile + honeypot + 5/IP/h rate-limit), confirm, preferences, unsubscribe and RFC 8058 one-click list-unsubscribe, plus tracking pixels and click-redirects.
Sys-CRUD covers lists, subscribers, CSV import with attested consent proof, GDPR deletion, MJML editor, visual segment builder, campaign wizard, automations, suppressions and deliverability. A/B testing uses pooled two-proportion z-test with Wilson confidence intervals; a winner-selection job picks the winner once significance is reached. Send-time optimisation builds an engagement_profile per subscriber (best_hour_utc, best_weekday) refreshed daily; the dispatcher schedules each recipient inside their optimal 24h window.
Drip automations use a react-flow visual builder with preset onboarding, re-engagement and win-back flows. Gemini 2.5 Pro powers an AI assist (subject lines, content polish, 5-persona preview) under a 50 req/operator/24h quota with prompt and response hashes audit-logged. Heatmaps aggregate click coordinates per campaign; cohorts compute engagement scores per subscriber and flag at-risk subscribers for re-engagement.
Litmus runs cross-client previews and spam-score checks, blocking schedule when spam score > 5. The campaign dispatcher runs every minute, throttles to throttle_per_hour, renders MJML, merges variables, rewrites links, injects the open pixel, appends the compliance footer and sets List-Unsubscribe headers. The distributor webhook verifies HMAC, dedupes on provider_event_id, suppresses on hard bounce, escalates after 5 consecutive soft bounces, and suppresses + flags on complaints.
Twelve premium templates ship out of the box. Embeddable signup forms exist in both Astro and Next.js with prop-driven layouts and 4-language i18n. Compliance is non-negotiable: consent proof per subscribe, CAN-SPAM physical address auto-injected, full audit trail, 3-year post-unsub retention.
A deliverability dashboard auto-halts sends when bounce > 2% or complaint > 0.1%.
Centrala funktioner
- EmailDistributor abstraction (Mailjet primary, ACS failover, Null dev) — provider-agnostic delivery
- HMAC-signed double opt-in with purpose-bound tokens and RFC 8058 one-click unsubscribe
- Visual segment builder and react-flow drip automations (onboarding, re-engagement, win-back)
- A/B testing with z-test and Wilson intervals; per-subscriber send-time optimisation
- Gemini-powered AI assist for subject lines, content polish and 5-persona previews
- Cross-client preview and spam-score check via Litmus, blocking sends above threshold
- Compliance baked in: consent proof, CAN-SPAM footer, audit trail, deliverability auto-halt
I praktiken
A marketer drafts a federation announcement campaign in the sys console. She picks the 'federation_update' premium template, edits MJML in the visual editor, and uses the AI assist to generate four subject-line variants — picks two for an A/B split. She drags a segment in the visual builder ('country in [FR, BE, CH] AND role = federation_admin AND active in last 90 days'), sees 1 247 recipients live.
She enables send-time optimisation, schedules for tomorrow morning, runs a Litmus preview (spam score 1.4, all clients render clean). The dispatcher fans out across the day inside each subscriber's optimal window. The winner-selection job picks variant B at 04:00 the next morning and sends it to the remaining 80% pool — open rate 38%, click rate 7%, complaints 0.02%.
Features i detta subsystem
23| ID | Status | Funktioner |
|---|---|---|
| F19.17.01 | Levererad | EmailDistributor-abstraktion med send_transactional / send_bulk / handle_webhook; MailjetDistributor + ACSDistributor (failover) + NullDistributor (dev/test) ✅ PL-T152 |
| F19.17.02 | Levererad | Beanie-modeller: NewsletterList (platform/tenant-scope), NewsletterSubscription (pending/active/unsubscribed/bounced/complained/suppressed), NewsletterTemplate (MJML-source + rendered cache), NewsletterSegment, NewsletterCampaign (draft/scheduled/sending/sent/paused/cancelled), NewsletterEvent (90 d TTL), NewsletterSuppression, NewsletterAutomationFlow, NewsletterAutomationEnrolment, NewsletterAIQuota ✅ PL-T152 |
| F19.17.03 | Levererad | Double-opt-in med HMAC-SHA256-signerade tokens (purpose-bound: confirm/prefs/unsub, TTL 14/30/90 d) — services/newsletter_tokens.py ✅ PL-T152 |
| F19.17.04 | Levererad | Publika endpoints: POST /public/newsletter/subscribe (Turnstile + honeypot + rate-limit 5/IP/h), GET /public/newsletter/confirm, GET/PUT /public/newsletter/preferences/{token}, POST /public/newsletter/unsubscribe/{token}, POST /public/newsletter/unsubscribe-all/{token}, POST /public/newsletter/list-unsubscribe/{token} (RFC 8058 one-click), GET /public/newsletter/open/{...}.png, GET /public/newsletter/click/{...}?url= ✅ PL-T152 |
| F19.17.05 | Levererad | Sys-CRUD: lists + subscribers + CSV-import med attesterad consent-proof + GDPR-radering + templates (MJML-editor) + segments (visual builder) + campaigns (wizard) + automations + suppressions + deliverability + analytics-overview ✅ PL-T152 |
| F19.17.06 | Levererad | A/B-testing: variants + random-N% / full-random-split + pooled two-proportion z-test + Wilson confidence interval + winner-selection-job (services/newsletter_stats.py) ✅ PL-T152 |
| F19.17.07 | Levererad | Send-time-optimering: engagement_profile = {best_hour_utc, best_weekday, confidence} per subscriber; dagligt refresh-job + dispatcher som schemalägger per-recipient inom 24h-fönster, fallback till scheduled_at vid confidence < 0.3 ✅ PL-T152 |
| F19.17.08 | Levererad | Drip-automations: AutomationFlow med visual flow-builder (react-flow, trigger/wait/send/branch/exit); preset-flows: 7-day onboarding, 30-day re-engagement, 90-day win-back; triggers: subscription_confirmed, webinar_registered, tenant_onboarded, user_inactive_days, manual_enroll ✅ PL-T152 |
| F19.17.09 | Levererad | Dynamic content blocks i MJML: {% content-block key variants=[...] %} med send-time-evaluation per subscriber-context (språk/tenant/merge_data) ✅ PL-T152 |
| F19.17.10 | Levererad | AI-assist via Gemini 2.5 Pro: /sys/newsletter/ai/subject-lines, /ai/content-polish, /ai/preview-audience-reaction (5 personas); quota 50 req/operator/24h via newsletter_ai_quota; alla calls audit-loggade med prompt+response-hash ✅ PL-T152 |
| F19.17.11 | Levererad | Heatmaps + cohort-analytics: click-koordinat-aggregation per campaign, open/click-rate per cohort (tenant/role/language/source), engagement-score per subscriber (rolling 30d), at-risk-flagg (< 0.1 → feeder till re-engagement-automation) ✅ PL-T152 |
| F19.17.12 | Levererad | Cross-client-preview via Litmus API: screenshots per klient (Gmail, Apple Mail, Outlook, Yahoo) + spam-score-check; blockerar schedule vid spam-score > 5 ✅ PL-T152 |
| F19.17.13 | Levererad | Visuell segment-builder: drag-and-drop AND/OR-grupper, live recipient-count, sparbara segments; allow-list av fält + ops (eq, neq, in, nin, gt/gte/lt/lte, regex escaped, exists); services/newsletter_segment.py ✅ PL-T152 |
| F19.17.14 | Levererad | Campaign-dispatcher-job (newsletter-campaign-dispatcher, 1 min-cadence): throttled send enligt throttle_per_hour (default 5000/h); per recipient: MJML-render + merge-vars + link-rewrite + pixel-inject + compliance-footer + List-Unsubscribe-header + RFC 8058-one-click; pause/resume/cancel ✅ PL-T152 |
| F19.17.15 | Levererad | Distributor-webhook: POST /_internal/distributor-webhook/{provider} med HMAC-signaturverifiering, idempotent på provider_event_id; hard_bounce → suppression; soft_bounce-escalation efter 5 consecutive; complaint → suppression + status=complained ✅ PL-T152 |
| F19.17.16 | Levererad | Premium-template-gallery (12+ pre-designed): announcement, digest, event_invite, product_drop, case_study, win_back, re_engagement, onboarding_step, tournament_recap, scoreboard_release, federation_update, seasonal — clone-to-customize ✅ PL-T152 |
| F19.17.17 | Levererad | Embeddable signup-form — Astro (www/src/components/conversion/NewsletterSignup.astro) + Next.js (web/src/components/blocks/NewsletterSignupBlock.tsx); prop listSlug, layouts (inline/modal/card), 4-språk i18n, Turnstile + honeypot ✅ PL-T152 |
| F19.17.18 | Levererad | Preference-center-sida: www/src/pages/{en,sv,fr,es}/newsletter/preferences.astro med query-string-token, lista av lists i samma scope, name/language/per-list-membership, unsubscribe-all ✅ PL-T152 |
| F19.17.19 | Levererad | Webinar-integration (T151): GDPR-strikt default-unticked opt-in på POST /public/webinars/{slug}/register; vid kryss skapas NewsletterSubscription(status=pending, source=webinar_registration) parallellt med webinar-confirmation ✅ PL-T152 |
| F19.17.20 | Levererad | Tenant-admin read-only-vy: admin/app/(dashboard)/newsletter/{index,[id]}.tsx + /admin/newsletter/{lists,lists/{id}/subscribers,campaigns}-API scoped via request.state.tenant_id (403 utan tenant-kontext) ✅ PL-T152 |
| F19.17.21 | Levererad | Compliance: consent-proof per subscribe (IP-hash + UA-hash + form_id + timestamp + method), CAN-SPAM physical-address auto-inject i footer, audit-trail för subscribe/confirm/preferences/unsubscribe/import/suppression/GDPR, 3-year post-unsub retention ✅ PL-T152 |
| F19.17.22 | Levererad | Deliverability-dashboard: per from-domain reputation + SPF/DKIM/DMARC-status + bounce/complaint/unsub 30d-rater + spam-score-historik; halt-sends-action vid threshold-breach (bounce > 2 % eller complaint > 0.1 %) ✅ PL-T152 |
| F19.17.23 | Levererad | Tester: 35 testfall i api/tests/test_t152_newsletter.py — unit (z-test, Wilson, pick_winner, tokens, segment DSL), integration (full subscribe→confirm→prefs→unsub-cykel, suppression, honeypot, rate-limit 429, tenant-admin 403), alla passerar ✅ PL-T152 |
Relaterade subsystem
Intressenter som behöver detta subsystem
Förekommer i 2 intressentanalyser