How to send a custom e-mail?

By default, Sylius only sends emails for essential flows like order confirmation. However, you can easily extend this by configuring custom logic and templates using the built-in SyliusMailerBundle.

This guide shows how to send an email to all administrators when a product variant becomes out of stock.


1: Create the Email Template

This file defines how your email will look. Sylius expects a subject block and a content block.

{# templates/email/out_of_stock.html.twig #}

{% extends '@SyliusCore/Email/layout.html.twig' %}

{% block subject %}
    One of your products is out of stock.
{% endblock %}

{% block content %}
    <div style="text-align: center; margin-bottom: 30px;">
        The variant
        <div style="margin: 10px 0;">
            <span style="border: 1px solid #eee; padding: 10px; color: #1abb9c; font-size: 28px;">
                {{ variant.name }}
            </span>
        </div>
        is currently out of stock.
    </div>
{% endblock %}

2: Register the Email in Mailer Configuration

This registers your custom email under the Sylius Mailer system.

# config/packages/sylius_mailer.yaml

sylius_mailer:
    sender:
        name: Sylius Example Store
        address: [email protected]
    emails:
        out_of_stock:
            subject: 'A product is out of stock!'
            template: 'email/out_of_stock.html.twig'

3: Create a Custom Email Manager

This class orchestrates stock checking and email sending.

<?php

// src/EmailManager/OutOfStockEmailManager.php

namespace App\EmailManager;

use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
use Sylius\Component\Mailer\Sender\SenderInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;

readonly final class OutOfStockEmailManager
{
    public function __construct(
        private SenderInterface $emailSender,
        private AvailabilityCheckerInterface $availabilityChecker,
        private RepositoryInterface $adminUserRepository,
    ) {
    }

    public function sendOutOfStockEmail(OrderInterface $order): void
    {
        $admins = $this->adminUserRepository->findAll();
        if (empty($admins)) {
            return;
        }

        foreach ($order->getItems() as $item) {
            $variant = $item->getVariant();

            if (!$this->availabilityChecker->isStockSufficient($variant, 1)) {
                $this->notifyAdminsAboutOutOfStock($admins, $variant, $order);
            }
        }
    }

    private function notifyAdminsAboutOutOfStock(array $admins, ProductVariantInterface $variant, OrderInterface $order): void
    {
        foreach ($admins as $admin) {
            $this->emailSender->send(
                'out_of_stock',
                [$admin->getEmail()],
                [
                    'variant' => $variant,
                    'channel' => $order->getChannel(),
                    'localeCode' => $order->getLocaleCode(),
                ],
            );
        }
    }
}

4: Register the Service

Add this to your config/services.yaml:

# config/services.yaml

services:
    App\EmailManager\OutOfStockEmailManager:
        arguments:
            - '@sylius.email_sender'
            - '@sylius.checker.inventory.availability'
            - '@sylius.repository.admin_user'

5: Create a callback for order_payment

Create the Event Listener

<?php

// src/EventListener/Workflow/OrderPayment/SendEmailWithOutOfStockListener.php

declare(strict_types=1);

namespace App\EventListener\Workflow\OrderPayment;

use App\EmailManager\OutOfStockEmailManager;
use Sylius\Component\Core\Model\OrderInterface;
use Symfony\Component\Workflow\Event\CompletedEvent;
use Webmozart\Assert\Assert;

final class SendEmailWithOutOfStockListener
{
    public function __construct(private OutOfStockEmailManager $outOfStockEmailManager)
    {
    }

    public function __invoke(CompletedEvent $event): void
    {
        $order = $event->getSubject();
        Assert::isInstanceOf($order, OrderInterface::class);

        $this->outOfStockEmailManager->sendOutOfStockEmail($order);
    }
}

Register the Listener

# config/services.yaml

services:
    app.listener.workflow.order_payment.send_email_with_out_of_stock:
        class: App\EventListener\Workflow\OrderPayment\SendEmailWithOutOfStockListener
        tags:
            - { name: kernel.event_listener, event: workflow.sylius_order_payment.completed.pay, priority: 100 }

✅ Test the Results

1. Create a product with tracked stock

  • Go to the Admin PanelCatalogProducts Create .

  • Create a simple product and reduce one of its variant stock levels to 3.

  • Ensure that the product is tracked, and the stock is managed (i.e., onHand: 3, tracked: true).

2. Place an Order

  • From the shop, place an order for this product, quantity 3.

  • Open the admin panel and complete the payment on the placed order.

3. Verify Email Sent

You can use one of these tools to check if the email was sent:

  • MailHog (local): Quick to run via Docker, view at localhost:8025.

  • Mailtrap (cloud): Great for shared environments. See mailtrap.io.

  • Symfony Mailer Profiler: In dev mode, emails appear under the Mailer tab in Symfony Profiler.

Last updated

Was this helpful?