tenbruggencate / newsletter-lite
Shopware 6 newsletter signup: GDPR-safe with double opt-in, opaque-token unsubscribe with email erasure, per-IP rate limit.
Package info
bitbucket.org/Bruggencate/sw-plugin-newsletter-lite
Type:shopware-platform-plugin
pkg:composer/tenbruggencate/newsletter-lite
Requires
- php: >=8.1
- shopware/core: ~6.7
- shopware/storefront: ~6.7
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
Suggests
- tenbruggencate/newsletter-pro: Campaign editor, live product blocks & order-data segmentation (paid)
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 1.17.0
- 1.16.0
- 1.15.1
- 1.15.0
- 1.14.2
- 1.14.1
- 1.14.0
- 1.13.0
- 1.12.2
- 1.12.1
- 1.12.0
- 1.11.0
- 1.10.3
- 1.10.2
- 1.7.1
- 1.7.0
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.0
- 1.4.1
- 1.4.0
- dev-feat/dal-writes-and-flow-events
- dev-feat/doi-mail-through-shopware-mail-templates
- dev-fix/result-page-title-per-outcome
- dev-feat/signup-heading-level
- dev-fix/w1-docs-truth
- dev-docs/readme-more-from-links
- dev-feat/newsletter-hreflang
- dev-feat/newsletter-seo-sitemap
- dev-docs/newsletter-pro-released
- dev-fix/funnel-url-repoint
- dev-feat/tb-upgrade-banner
- dev-docs/lite-pro-strategy-resync-2
- dev-docs/lite-pro-strategy-resync
- dev-docs/cross-promotion
- dev-feat/subscriber-sales-channel
- dev-docs/readme-branding
- dev-chore/reposition-newsletter-lite
- dev-docs/reposition-lite-pro-family
- dev-chore/refresh-screenshots-v1.10.3
- dev-fix/admin-dal-timestamps-v1.10.2
- dev-docs/readme-admin-module
- dev-feat/subscriber-admin-module
- dev-feat/audit-improvements
- dev-feat/admin-config-improvements
- dev-chore/docs-screenshots-v1.7.1
- dev-chore/ux-polish-v1.7.0
- dev-chore/packagist-description-and-screenshot-cleanup
- dev-fix/scss-autoload-and-doc-refresh
- dev-feat/double-opt-in
- dev-feat/subscribe-rate-limit
- dev-chore/trigger-packagist-webhook
- dev-release/v1.4.1
- dev-chore/gitignore-claude-skills
- dev-ci/junit-test-reports
- dev-polish/store-icon
- dev-polish/audit-screenshots
- dev-polish/multi-locale-docs
- dev-polish/multi-locale-changelogs
- dev-polish/why-comparison-related
- dev-fix/cross-origin-subscribe-protection
- dev-docs/clarify-token-generation
This package is auto-updated.
Last update: 2026-09-05 16:35:34 UTC
README
Newsletter Lite
![]()
Own a clean, GDPR-safe subscriber list. Newsletter Lite is the genuine free tool for capturing and managing newsletter subscribers in Shopware 6: a standalone subscriber table, opaque-token unsubscribe with one-click email erasure, double opt-in by default, and a searchable admin list with CSV export. No third-party dependencies. None of Shopware's built-in newsletter UX-customisation pain.
It does one thing, and does it well — get consent-clean subscribers into a list you control, and let you get them back out again (CSV export) for whatever you send them with.
▲ Newsletter Pro is released. It adds the sending side — a campaign editor, live product blocks from your catalogue and segmentation on your shop's own order data — and installs side-by-side with this plugin. See Part of a Lite/Pro family below, or the Pro overview.
License: MIT · Shopware: 6.7.x · PHP: 8.1 / 8.2
🇬🇧 English · 🇳🇱 Nederlands · 🇩🇪 Deutsch
📖 More context: Why we built this · How it compares
Screenshots
Landing page — desktop |
Landing page — mobile (responsive) |
Admin — subscriber management — searchable list, CSV export, double-opt-in status badges |
|
What it does
Shopware ships a newsletter module, but customising its UX fights the built-in flow. This plugin is deliberately smaller:
tenbruggencate_nl_subscribertable — plainid,email,source,brand_name(server-derived — see below),consent_given+ audit trail,confirmed+ DOI token columns,unsubscribe_token,locale,subscribed_at,unsubscribed_at- Signup form partial — drop-in Twig include with compact (footer) and full (landing) variants
- Landing page at
/newsletter— SEO-safe, standalone, no theme dependency - Opaque-token unsubscribe — URL carries a CSPRNG token, not the email address. One click: email gets scrubbed to
erased-<id>@newsletter.invalid, the row stays for stats - Three locales out of the box — nl-NL, en-GB, de-DE
- Double opt-in by default (v1.6+) — new signups receive a confirmation email; rows only flip to
confirmed=1after the visitor clicks the link. 24h token expiry. Existing pre-DOI subscribers grandfathered as already confirmed. Single-opt-in remains available via therequireDoubleOptInconfig toggle if you have a deliberate reason to use it - House-style confirmation e-mail (v1.16.0) — the double-opt-in mail is a regular Shopware e-mail template: wrapped in the sales channel's header and footer, editable per language under Settings → Email templates (preview + test send), never overwritten by the plugin. See "Apply your house style to the confirmation e-mail" below
- Origin / volume / consent triple-defence on the subscribe endpoint — cross-origin POSTs rejected (v1.4.1), per-IP rate limit (v1.5.0, 5 attempts / 10 min), and DOI for proof-of-consent (v1.6.0)
- Admin subscriber list (v1.10.0) — a "Newsletter subscribers" module under the Marketing menu: search by email, sortable columns, a per-row confirmed / pending / unsubscribed status, and one-click CSV export. Backed by a DAL entity — the opaque tokens and consent IP hash are never exposed through the admin API
- Flow Builder triggers (v1.17.0) — subscribed, confirmed and unsubscribed, each with the subscriber's address as mail recipient, so a welcome mail or a CRM webhook is a flow, not code. See "Flow Builder" below
Install
composer require tenbruggencate/newsletter-lite
bin/console plugin:refresh
bin/console plugin:install --activate TenBruggencateNewsletterLite
bin/console database:migrate --all TenBruggencateNewsletterLite
bin/console cache:clear
Configuration
Configurable per sales channel from Extensions → TenBruggencateNewsletterLite.
| Field | Default | Purpose |
|---|---|---|
enabled | true | Kill switch — signup form renders nothing when off |
brandName | (empty) | Shown in headings (e.g. "Acme Club"); leave blank for generic wording |
consentText | (snippet) | Checkbox label; HTML allowed — rendered through Shopware's sw_sanitize (links with href, target and title, strong, em, br and the other basic formatting tags survive; scripts, event-handler attributes and javascript: URLs are stripped) |
privacyPageUrl | /about/privacy | Link target in the fineprint |
landingPageEnabled | true | Toggle the /newsletter landing page route |
signupHeadingLevel | h2 | Heading level of the form's own title (h2 / h3 / h4) — keep h2 so the footer form doesn't skip a level under the page's h1 (WCAG 1.3.1 heading order) |
Apply your house style to the confirmation e-mail
Since 1.16.0 the double-opt-in confirmation is a regular Shopware e-mail template, so it picks up the same house style as your order confirmations — no code involved.
- Header and footer — go to Settings → Email templates → Header and footer. Create or edit a set with your logo, colours and footer text and assign it to your sales channel(s). Every e-mail Shopware sends for that sales channel — this one included — is wrapped in it.
- The template itself — go to Settings → Email templates → Email templates and open the type Newsletter confirmation (Newsletter Lite) (technical name
tenbruggencate_newsletter_confirm). Edit the subject, the HTML body and the plain-text body per language, use Preview to check the result and Send test email to receive it in your own inbox. Available variables:{{ confirmUrl }}(the confirmation link — keep it in),{{ brand }}(the resolved brand name),{{ hours }}(how long the link stays valid) and{{ salesChannel }}(for example{{ salesChannel.name }}). - Your edits are never overwritten — the plugin creates the template once, when its migrations run, and never touches a template that already exists. Plugin updates keep your version.
If the template cannot be found (for example because database:migrate has not run yet), the plugin falls back to its built-in plain-text e-mail and writes a warning naming tenbruggencate_newsletter_confirm to the log, so no signup is left without a confirmation mail.
Flow Builder
Since 1.17.0 the plugin dispatches three events that appear as triggers in Settings → Flow Builder (in the trigger tree under Ten Bruggencate → Newsletter Lite), next to Shopware's own newsletter triggers. Each one is dispatched only after the subscriber row has been written, so a flow always sees the final state.
| Trigger | Event name | Class | Fires when |
|---|---|---|---|
| Subscribed | tenbruggencate.newsletter_lite.subscribed | NewsletterLiteSubscribedEvent | A visitor submitted the form and a new subscriber was created — with double opt-in still pending (confirmed = false) or, with single opt-in, already active (confirmed = true) |
| Confirmed | tenbruggencate.newsletter_lite.confirmed | NewsletterLiteConfirmedEvent | A subscriber clicked a valid, unexpired double-opt-in link (not on a repeat click, not on an expired link) |
| Unsubscribed | tenbruggencate.newsletter_lite.unsubscribed | NewsletterLiteUnsubscribedEvent | A subscriber used their unsubscribe link and the address was erased (not on a repeat visit) |
All three are MailAware — the Send email action works out of the box with the subscriber's address as the recipient — and SalesChannelAware, so a flow can branch on the sales channel. The data below is available to actions and e-mail templates as plain variables (for example {{ email }}):
| Variable | Type | Meaning |
|---|---|---|
email | string | The subscriber's address. On the unsubscribed trigger this is the address before erasure (the row itself now holds erased-<id>@newsletter.invalid), so a goodbye mail is still possible; the plugin stores it nowhere |
brandName | string | The brand the subscriber signed up under (see "Related plugins") |
source | string | footer, landing, checkout or other |
locale | string | The storefront locale at signup, e.g. en-GB |
salesChannelId | string | The sales channel of the request |
subscribedAt | string | Signup time, ISO 8601 (UTC) |
confirmed | bool | Subscribed only — false while double opt-in is pending |
confirmedAt | string | Confirmed only — ISO 8601 (UTC) |
unsubscribedAt | string | Unsubscribed only — ISO 8601 (UTC) |
The opaque unsubscribe / confirmation tokens and the consent IP hash are never part of an event. PHP code can listen on the classes in TenBruggencateNewsletterLite\Event\ like on any Symfony event.
The double-opt-in confirmation mail is still sent by the plugin itself (see above). The triggers are for additional automation: a welcome mail after confirmation, a customer tag, a webhook to your CRM.
Routes
| Route | Method | Purpose |
|---|---|---|
/newsletter | GET | Landing page with signup form |
/newsletter/subscribe | POST | Handles the signup form submit |
/newsletter/unsubscribe/{token} | GET | Opaque-token unsubscribe + email scrub |
Usage
Embed the signup form anywhere via Twig:
{# Compact variant — for footers #}
{% sw_include '@TenBruggencateNewsletterLite/storefront/component/newsletter/signup-form.html.twig' with {
source: 'footer',
compact: true
} only %}
{# Default variant — for landing pages #}
{% sw_include '@TenBruggencateNewsletterLite/storefront/component/newsletter/signup-form.html.twig' %}
The partial reads its heading level from the signupHeadingLevel config field.
Override it per include with nlHeadingLevel when your theme wraps the form in
a section that already has its own heading — allowed values are h2, h3 and
h4; anything else falls back to h2, so an unvalidated value can never end up
as a tag name:
{% sw_include '@TenBruggencateNewsletterLite/storefront/component/newsletter/signup-form.html.twig' with {
source: 'checkout',
nlHeadingLevel: 'h3'
} only %}
Standards
- Performance — no JS dependency; plain
POSTwith a 302 redirect on success. Async submission viafetch()is a ~20-line enhancement you ship in your theme if desired. - SEO — add
/newsletter/unsubscribe/*to your theme'srobots.txtdisallow list (the landing page is indexable, the unsubscribe endpoint should not be). - GDPR — the opaque-token pattern means the unsubscribe URL leaks nothing. One click also erases the email address from storage; the row stays for stats, PII is gone. Satisfies right-to-erasure without a manual request. Every signup writes a consent audit trail (
consent_given_at,consent_text_version,consent_ip_hash) for Article-7 supervisory audits. Full data-flow + subject-rights documentation inGDPR.md. - WCAG 2.2 AA — semantic
<form>,<label for>,<input type=email required>, keyboard-accessible by construction. Focus state inherited from your theme. Live axe-core audit output + desktop + mobile screenshots + localised-copy evidence:docs/ACCESSIBILITY.md. - Security — CSRF-protected via Shopware's default handler;
unsubscribe_tokenis 24 bytes fromrandom_bytes(), hex-encoded to a 48-character string. No user-controllable input in the unsubscribe SQL (token is looked up with a prepared statement). - Uninstall —
plugin:uninstall --keep-user-datapreserves thetenbruggencate_nl_subscribertable AND everyTenBruggencateNewsletterLite.config.*row (default, recommended path when you might reinstall).plugin:uninstallwithout the flag drops the subscriber table entirely + clears config — use this only when you genuinely want to discard all subscribers. There is no middle ground; exports happen before, not during, uninstall.
Production-readiness checklist
A deliberately short list of things to verify before enabling the plugin on a customer-facing storefront. Not legal cover — the MIT license already disclaims warranty — but practical guidance an experienced operator would want anyway.
- [ ] Database backup before upgrading or uninstalling. The plugin owns one table; a backup makes the destructive uninstall path reversible.
- [ ] SMTP wired up before flipping
requireDoubleOptInon. v1.6.0+ defaults DOI to on; without working mail transport, visitors submit the form, get the "check your inbox" page, and never receive a confirmation email — they're stuck inconfirmed=0forever. If you can't configure SMTP yet, setrequireDoubleOptIn=falseuntil you can. - [ ] Privacy policy URL set in the plugin config. The form's fineprint links to it; an empty URL routes to a 404 and trains visitors to ignore privacy notices.
- [ ] Test on staging before production. Especially the unsubscribe + DOI confirm flows — both rely on per-row tokens that depend on
random_bytes()being available (always is, on any modern PHP, but verify). - [ ] Trusted proxies configured if you're behind a CDN / load balancer. v1.5.0+ rate-limits per client IP; without
trusted_proxiesset, every visitor shares the same proxy IP and the limit fires too aggressively.
Compatibility
Core platform
| Shopware | PHP | Status |
|---|---|---|
| 6.7.x — tested against 6.7.8, 6.7.9 | 8.1, 8.2 | Stable |
| 6.6.x | — | Not supported |
| 6.5.x and earlier | — | Not supported |
Database
| Engine | Version | Notes |
|---|---|---|
| MySQL | 8.0+ | Primary target; JSON functions used for config-row manipulation in migrations |
| MariaDB | 10.11+ | Tested end-to-end; earlier versions lack some JSON operator support |
Browsers (storefront)
Evergreen browsers only — the two most recent stable releases of each:
| Browser | Desktop | Mobile |
|---|---|---|
| Chrome / Chromium | ✅ | ✅ |
| Firefox | ✅ | ✅ |
| Safari | ✅ (macOS) | ✅ (iOS 16+) |
| Edge | ✅ | — |
Internet Explorer and legacy Edge are not supported. The plugin emits no runtime JS (where applicable) so graceful degradation on older browsers usually still renders content, just without progressive enhancements.
Admin browsers
Same evergreen matrix — the Shopware admin is Vue-based and has its own compatibility baseline that this plugin doesn't extend or narrow.
Development
| Tool | Version | Scope |
|---|---|---|
| PHP | ≥ 8.1 | Runtime + test suite |
| Composer | 2.x | Dependency management |
| Node.js | ≥ 18 | Only needed if you edit SCSS and re-run the theme compile |
| Python | ≥ 3.9 | Only needed for the repo's asset-pipeline scripts (scripts/*.py) |
Accessibility
WCAG 2.2 level A + AA — see docs/ACCESSIBILITY.md for axe-core audit output and per-page violations.
What we test before each release
- Full PHPUnit unit suite against PHP 8.1 + 8.2 (source-inspection tests don't need a kernel)
- PHPStan level 8 + PHP-CS-Fixer (@PSR12 + @Symfony)
- Composer validate on every plugin
- Live-DB smoke tests (plugin install → activate → route render → uninstall cycle)
- axe-core audit on the primary storefront surfaces (see ACCESSIBILITY.md)
Part of a Lite/Pro family
Newsletter Lite is the free tier of a Lite/Pro plugin family. It is a complete, standalone tool — capturing and managing a clean subscriber list is the whole job, and nothing here is gated or crippled to push an upsell.
Newsletter Pro is released and sold separately. It requires this plugin and installs side-by-side with it, building on the same subscriber table — tenbruggencate/newsletter-pro is not published on Packagist: it is delivered directly today, with a Shopware Store listing in preparation.
What Pro adds:
- Campaign editor with subject, preheader and content: save, schedule or send now from Marketing → Newsletter Pro
- Product blocks: new arrivals, a category or bestsellers pulled live from your catalogue into the email, per sales channel
- Segmentation on your own order data: has ordered, last order older than X days, total spend above an amount
- Live preview — "matches N subscribers" before every send; only confirmed, active addresses are mailed
- Reliable sending through Shopware's own mail stack, in throttled background batches, with a status per recipient
- Export everything (campaigns, segments, send logs and settings) as one JSON file, so you are never locked in
→ Newsletter Pro — features and pricing
If all you need today is a consent-clean subscriber list, Newsletter Lite on its own is the right tool.
Related plugins
Part of the TenBruggencate Suite — small, focused plugins that play well together:
- MultiBrand — recommended: when MultiBrand is active,
brand_nameis populated automatically from the resolved brand (priority: MultiBrand resolver → SalesChannelContext → configuredbrandName→ empty). Never read from the form, so tampering is impossible. Per-SC segmentation without separate sales channels. - Analytics — signup conversions tracked automatically when Analytics is active and consent is given.
- LegalPages — provides the
/legal/privacypage thatprivacyPageUrltypically points at. - Maintenance — branded maintenance page that cooperates with this plugin's privacy / consent posture.
More from Ten Bruggencate Development
Focused, privacy-first Shopware 6 plugins — free to start, upgrade when you grow:
- Newsletter — Lite: GDPR-safe signup & subscriber-list management · Pro: campaigns, behavioural segmentation, product-pull blocks
- Legal Pages — Lite: localized legal-page templates · Pro: compliance toolkit (cookie consent, accessibility statement, imprint validator, audit trail)
- Analytics — Lite: multi-backend (Matomo / Plausible), GDPR-first · Pro: e-commerce conversion tracking
- Maintenance — Lite: branded, SEO-correct (HTTP 503) maintenance page · Pro: scheduled maintenance windows
- Multi-Brand — Lite: serve multiple brands from one Shopware, by hostname · Pro: per-brand transactional email overrides
- Product Encyclopedia — Lite: structured educational content pages · Pro: per-profile related-product cross-links
- Seasons — Lite: scheduled theme-config variants · Pro: per-variant marketing banners
Need more than a plugin? Ten Bruggencate Development builds and customizes Shopware 6 stores. → Let's talk
Support
- Email: guy@tenbruggencatedevelopment.nl
- Docs & news: tenbruggencatedevelopment.nl
- Source / issues: Bitbucket repo
- Security vulnerabilities: see
SECURITY.md— email first, no public issues, 72-hour acknowledgement SLA