Markus Michalski FAQ

Newsletter Management: Extending

Extend the newsletter plugin for Sylius - PRE_SUBSCRIBE event for spam protection, form extension via FormTypeExtension, template overrides and notes on replacing services

Deutsche Version

Back to overview

Newsletter Management: Extending

This documentation always describes the latest published version.

The plugin offers two documented extension points for third parties: the PRE_SUBSCRIBE event, with which a listener can reject signups, and the signup form NewsletterSignupType, to which fields can be added through a FormTypeExtension. The ALTCHA plugin uses both for spam protection. Template overrides cover the look.

The PRE_SUBSCRIBE event

Property Value
Constant Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionEvents::PRE_SUBSCRIBE
Event name mmd_sylius_newsletter_management_plugin.newsletter_subscription.pre_subscribe
Payload Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionPreSubscribeEvent
When Before anything is stored or sent, right after the check that the newsletter is enabled for the channel

The NewsletterSubscriptionManager dispatches the event for every signup that goes through it: the signup page (/newsletter), signing up through the Shop API and the newsletter checkbox on registration and profile. The footer teaser form does not dispatch it itself, it only forwards to the signup page. Unsubscribing, confirming and changing preferences do not dispatch the event.

The payload offers:

Method Description
getEmail() The address, already lowercased and trimmed
getChannel() The channel of the signup
getRequest() The current request, for headers, body fields or the client address
getSource() The entry point as a NewsletterSubscriptionSource: Storefront, Api, Registration or Admin
reject(?string $reason = null) Rejects the signup. $reason is a translation key shown to the customer, or null for the default text
isRejected(), getRejectionReason() Query the state, for example from further listeners

A rejection shows an error message (flash) in the storefront and answers the Shop API request with 422.

Example: a listener that rejects disposable domains

<?php

declare(strict_types=1);

namespace App\Newsletter;

use Mmd\SyliusNewsletterManagementPlugin\Entity\NewsletterSubscriptionSource;
use Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionEvents;
use Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionPreSubscribeEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener(event: NewsletterSubscriptionEvents::PRE_SUBSCRIBE)]
final class BlockDisposableDomains
{
    private const BLOCKED = ['mailinator.com', 'trashmail.com'];

    public function __invoke(NewsletterSubscriptionPreSubscribeEvent $event): void
    {
        // Registration has its own protection.
        if ($event->getSource() === NewsletterSubscriptionSource::Registration) {
            return;
        }

        $domain = substr((string) strrchr($event->getEmail(), '@'), 1);
        if (\in_array($domain, self::BLOCKED, true)) {
            $event->reject('app.newsletter.disposable_domain');
        }
    }
}

With the #[AsEventListener] attribute and autoconfigure enabled, the class in your project is enough. Alternatively, register the listener with a tag:

services:
    App\Newsletter\BlockDisposableDomains:
        tags:
            - { name: kernel.event_listener, event: 'mmd_sylius_newsletter_management_plugin.newsletter_subscription.pre_subscribe' }

You add the translation key from the example to your own translation file, for example translations/flashes.en.yaml. In the storefront the key is translated as a flash message. If you pass null, the plugin's default text appears.

Sylius core's newsletter checkbox (registration and profile) dispatches the event with the source Registration. Sylius' own Shop API registration (POST /shop/customers) sets the core flag without an event and without the plugin creating a signup. Headless clients sign up through POST /shop/newsletter-subscriptions.

Extending the form

Mmd\SyliusNewsletterManagementPlugin\Form\Type\NewsletterSignupType is deliberately not final. That lets you attach fields to the signup form with a Symfony FormTypeExtension. This is how the ALTCHA plugin adds its proof-of-work widget, while the server-side check listens on PRE_SUBSCRIBE.

<?php

declare(strict_types=1);

namespace App\Form\Extension;

use Mmd\SyliusNewsletterManagementPlugin\Form\Type\NewsletterSignupType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

final class NewsletterSignupExtension extends AbstractTypeExtension
{
    public static function getExtendedTypes(): iterable
    {
        return [NewsletterSignupType::class];
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('companyWebsite', TextType::class, ['mapped' => false, 'required' => false]);
    }
}

An unmapped field does not end up in the NewsletterSignup data model; you read it from $event->getRequest() in your PRE_SUBSCRIBE listener.

After the consent checkbox and before the submit button, the signup page renders the Twig hook mmd_altcha.widget.newsletter. It belongs to the ALTCHA plugin and stays empty while that plugin is missing or the protection target is not enabled. How to enable the target is described under ALTCHA: Configuration.

Overriding templates

You customize the look of the signup page, confirmation, preferences, unsubscribe page, footer teaser and mails with the regular Sylius mechanism: place a template with the same path under templates/bundles/MmdSyliusNewsletterManagementPlugin/ in your project or theme. Which templates and hooks exist is listed under Storefront integration. The admin templates are interface internals of the plugin and not meant to be overridden.

Which placeholders newsletter authors may use in the editor is described on the Newsletters page.

Replacing services

The plugin's services are wired through interfaces (for example NewsletterSubscriptionManagerInterface, NewsletterMailerInterface, NewsletterMailSenderInterface). You can replace or extend them with regular Symfony service decoration.

Technically that is possible, but it is not promised as a stable public API. The documented extension points are PRE_SUBSCRIBE and NewsletterSignupType. Interface methods can change, so re-check a decoration with every update.