Markus Michalski FAQ

Newsletter Management: Technical details

Architecture, plugin structure, tables and entities, migrations, design decisions, tests and development workflow of the Sylius newsletter management plugin

Deutsche Version

Back to overview

Newsletter Management: Technical details

This documentation always describes the latest published version.

This page describes the structure and internals of the plugin for integrators and developers: how signup and sending flow, the layout, the tables and the most important design decisions.

Architecture

Signups go through exactly one entry point, the NewsletterSubscriptionManager. Whether it is the storefront form, the registration checkbox or the Shop API, all call the same methods to subscribe, confirm, update and unsubscribe. The footer teaser does not call the manager itself; it hands over to the form on /newsletter via GET. There is no second business logic for the API.

graph TD
    A["Storefront: form, registration"] --> M["NewsletterSubscriptionManager"]
    B["Shop API: processors and provider"] --> M
    M --> E{"PRE_SUBSCRIBE event"}
    E -->|"rejected"| R["Rejected: flash or 422"]
    E -->|"allowed"| P["Save subscription"]
    P --> ML["NewsletterMailer: confirmation, welcome, already subscribed"]
    P --> S["Sync of the customer newsletter flag"]
    P --> T["Tracking: consent and deletion"]

Sending runs separately, driven by cron:

graph LR
    C["Cron: mmd:newsletter:send"] --> SV["NewsletterSendingService"]
    SV --> D["Claim deliveries"]
    D --> MS["NewsletterMailSender"]
    MS --> RE["NewsletterRenderer"]
    RE --> TH["Theme of the channel"]
    RE --> LT["NewsletterLinkTracker"]
    MS --> TR["Symfony Mailer"]
    ADM["Admin: send, schedule, cancel"] --> SV

Plugin structure

sylius-newsletter-management/
β”œβ”€β”€ assets/            admin and shop assets
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ config.yaml
β”‚   β”œβ”€β”€ mailer.yaml    e-mail codes of the three subscriber mails
β”‚   β”œβ”€β”€ services.php   all services in one file
β”‚   β”œβ”€β”€ grids/         admin grids
β”‚   β”œβ”€β”€ routes/        admin.yaml, shop.yaml
β”‚   └── twig_hooks/    admin.yaml, shop.yaml
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Api/           Shop API: processors, provider, query extension
β”‚   β”œβ”€β”€ Command/       setup, send, subscription:purge, tracking:purge
β”‚   β”œβ”€β”€ Controller/    storefront, dashboard, preview, sending, tracking, export
β”‚   β”œβ”€β”€ DependencyInjection/
β”‚   β”œβ”€β”€ Entity/        entities, interfaces and enums
β”‚   β”œβ”€β”€ Event/         PRE_SUBSCRIBE
β”‚   β”œβ”€β”€ EventListener/ customer checkbox, delete guard
β”‚   β”œβ”€β”€ Factory/
β”‚   β”œβ”€β”€ Form/          form types and models
β”‚   β”œβ”€β”€ Mailer/        subscriber mails
β”‚   β”œβ”€β”€ Manager/       NewsletterSubscriptionManager
β”‚   β”œβ”€β”€ Menu/          admin menu
β”‚   β”œβ”€β”€ Migrations/    11 Doctrine migrations
β”‚   β”œβ”€β”€ Newsletter/    placeholders, renderer, text generator, theme, URLs
β”‚   β”œβ”€β”€ Repository/
β”‚   β”œβ”€β”€ Sender/        newsletter sending per recipient
β”‚   β”œβ”€β”€ Sending/       sending orchestration
β”‚   β”œβ”€β”€ Settings/      per-channel settings, privacy link, consent text
β”‚   β”œβ”€β”€ Statistics/    numbers for the dashboard and newsletter detail
β”‚   β”œβ”€β”€ Sync/          sync with the customer newsletter flag
β”‚   β”œβ”€β”€ Tracking/      link tracker, recorder, signer
β”‚   └── Twig/          Twig extension
β”œβ”€β”€ templates/         admin, emails, shop
β”œβ”€β”€ tests/             Unit, Integration, Functional, TestApplication
└── translations/      messages, flashes, validators (de, en)

Entities and tables

All tables carry the prefix mmd_sylius_newsletter_management_.

Entity Table Content
NewsletterSubscription ..._subscription One subscription per address and channel (unique). Status, source, tokens, timestamps, tracking consent, channel and customer (optional).
NewsletterCategory ..._category Category with code, position and an enabled switch
NewsletterCategoryTranslation ..._category_translation Translatable name and description
NewsletterChannelConfiguration ..._channel_configuration Per-channel settings (exactly one row per channel)
Newsletter ..._newsletter Subject, preheader, HTML, text, status, schedule, recipient count
NewsletterDelivery ..._delivery Delivery log: one row per newsletter and address (unique)
NewsletterLink ..._link Tracked links of a newsletter: code and URL
NewsletterTrackingHit ..._tracking_hit Anonymous counter per newsletter, day and target
NewsletterTrackingEvent ..._tracking_event A single event (open or click) of a consenting subscriber

There are also three join tables: ..._subscription_category (the categories of a subscription), ..._newsletter_category (a newsletter's audience) and ..._category_channel (the channels a category is offered in; empty = all).

erDiagram
    SUBSCRIPTION }o--o{ CATEGORY : "chooses"
    NEWSLETTER }o--o{ CATEGORY : "audience"
    CATEGORY ||--o{ CATEGORY_TRANSLATION : "translated by"
    NEWSLETTER ||--o{ DELIVERY : "delivery log"
    SUBSCRIPTION |o--o{ DELIVERY : "receives"
    NEWSLETTER ||--o{ LINK : "tracks"
    NEWSLETTER ||--o{ TRACKING_HIT : "counts"
    NEWSLETTER ||--o{ TRACKING_EVENT : "records"
    SUBSCRIPTION ||--o{ TRACKING_EVENT : "triggers"

Enums (PHP backed enums, no tables of their own):

Enum Values
NewsletterStatus draft, ready, scheduled, sending, sent, cancelled
NewsletterSubscriptionStatus pending, confirmed, unsubscribed
NewsletterSubscriptionSource storefront, api, registration, admin
NewsletterDeliveryStatus pending, sending, sent, failed, skipped
NewsletterTrackingMode off, anonymous, personal
NewsletterTrackingEventType open, click

Migrations

The plugin ships 11 migrations. They are applied with bin/console mmd:newsletter-management:setup (see Installation).

No. Purpose
1 Tables for subscriptions, categories (with translation), channel settings and the category assignment
2 First and last name on the subscription
3 Privacy policy page per channel
4 last_notified_at on the subscription (mail throttle)
5 Newsletter table with its category assignment
6 Channel restriction of categories
7 Sending: schedule and progress columns, delivery log, mailer transport per channel
8 Anonymous tracking: mode and retention, link table, daily counters
9 Personal tracking: consent, event table, consent text and retention per channel
10 Retention of unsubscribed subscriptions per channel
11 Retention of unconfirmed signups per channel

Design decisions

Tests

Directory Content
tests/Unit/ Unit tests: DependencyInjection, Entity, EventListener, Manager, Newsletter, Settings, Sync, Twig
tests/Integration/ Integration test of the setup command (Command)
tests/Functional/ Functional tests, for example signup flow, Shop API, sending, tracking, statistics, theme, cleanup commands
tests/TestApplication/ The Sylius test application (config, src, templates)

Development

Development happens exclusively inside the container (PHP 8.3, MySQL 8.4, nginx). A local PHP is not intended.

Command Purpose
make init Create the compose override, composer install, build the test application's frontend, start the containers
make database-init Create the database and run the migrations
make load-fixtures Load the Sylius fixtures (the functional tests need them)
make phpunit Unit and functional tests
make phpstan Static analysis
make ecs Code style
make up, make down, make clean Start and stop the containers, or remove them with their volumes
make php-shell Shell in the PHP container

These commands are for developing the plugin itself. You do not need them to run it in your shop.