Skip to content

Draft: 552 symfony mailer#430

Open
tijsverkoyen wants to merge 12 commits intomasterfrom
552-symfony-mailer
Open

Draft: 552 symfony mailer#430
tijsverkoyen wants to merge 12 commits intomasterfrom
552-symfony-mailer

Conversation

@tijsverkoyen
Copy link
Member

@tijsverkoyen tijsverkoyen commented Feb 27, 2026

Replace swiftmailer/swiftmailer with symfony/mailer

Todo

  • Check if Theme is used in the template. Probably not.
  • Check CI

Sending an email is as simple as:

  1. Create a Message instance via $this->get(EmailFactory::class)->create(). This wil return a preconfigured TemplatedEmail instance.
  2. Send the "message": $this->get(MailerInterface::class)>send($message);

The preconfigured TemplatedEmail instance has the default from, to and reply-to configured.

Full example:

        /** @var EmailFactory $emailFactory */
        $emailFactory = $this->get(EmailFactory::class);
        /** @var MailerInterface $mailer */
        $mailer = $this->get(MailerInterface::class);

        $message = $emailFactory->create()
            ->subject(FL::getMessage('RegisterSubject'))
            ->to(new Address(...))
            ->htmlTemplate('...')
            ->context([
                    ...
                ]
            );

        $mailer->send($message);

Good to know:

Summary by Sourcery

Migrate the application to Symfony Mailer with Twig extras, simplifying email configuration and enhancing email templating capabilities.

Enhancements:

  • Remove legacy Swiftmailer configuration, runtime mailer configurator, and SMTP/test-email admin UI in favor of DSN-based Symfony Mailer configuration.
  • Introduce an EmailFactory service that creates preconfigured TemplatedEmail instances using stored default from/to/reply-to settings.
  • Add a Twig UTM filter extension and register Twig Extra bundle and extensions for richer email and template handling.
  • Streamline the backend email settings page to only manage default from/to/reply-to addresses and adjust related rights and fixtures.

Build:

  • Configure Symfony Mailer DSN via framework.mailer in config and expose MailerInterface and EmailFactory as public services.
  • Add Symfony Mailer and Twig Extra packages to composer dependencies and register the Twig Extra bundle in the kernel.

Tests:

  • Update test database fixtures to remove obsolete Settings TestEmailConnection action entry.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 27, 2026

Reviewer's Guide

Migrates the application from SwiftMailer to Symfony Mailer, simplifies email settings to only manage from/to/reply-to addresses, wires Symfony Mailer + Twig extra bundles, and introduces a Twig utm filter plus an EmailFactory to create preconfigured TemplatedEmail instances.

Sequence diagram for creating and sending an email with EmailFactory and Symfony Mailer

sequenceDiagram
    actor BackendController
    participant EmailFactory as EmailFactory
    participant ModulesSettings as ModulesSettings
    participant Mailer as MailerInterface
    participant SmtpServer as Smtp_server

    BackendController->>EmailFactory: create()
    activate EmailFactory
    EmailFactory->>ModulesSettings: get(Core, mailer_from)
    ModulesSettings-->>EmailFactory: mailer_from[name,email]
    EmailFactory->>ModulesSettings: get(Core, mailer_to)
    ModulesSettings-->>EmailFactory: mailer_to[name,email]
    EmailFactory->>ModulesSettings: get(Core, mailer_reply_to)
    ModulesSettings-->>EmailFactory: mailer_reply_to[name,email]
    EmailFactory-->>BackendController: TemplatedEmail
    deactivate EmailFactory

    BackendController->>BackendController: configure subject,to,template,context

    BackendController->>Mailer: send(TemplatedEmail)
    activate Mailer
    Mailer->>SmtpServer: open_connection_and_deliver
    SmtpServer-->>Mailer: delivery_status
    Mailer-->>BackendController: void
    deactivate Mailer
Loading

Updated class diagram for EmailFactory and UtmExtension

classDiagram
    class ModulesSettings {
    }

    class TemplatedEmail {
    }

    class Address {
    }

    class EmailFactory {
        - ModulesSettings modulesSettings
        + __construct(ModulesSettings modulesSettings)
        + create() TemplatedEmail
        - createDefaultEmailInstance() TemplatedEmail
    }

    EmailFactory --> ModulesSettings : uses
    EmailFactory --> TemplatedEmail : creates
    EmailFactory --> Address : uses

    class AbstractExtension {
    }

    class TwigFilter {
    }

    class UtmExtension {
        + getFilters() array
        + injectUtmTags(string html, string source, string medium, string campaign) string
    }

    UtmExtension --|> AbstractExtension
    UtmExtension --> TwigFilter : returns

    class MailerInterface {
    }

    BackendController o--> EmailFactory : obtains
    BackendController o--> MailerInterface : obtains

    class BackendController {
        + sendRegistrationEmail()
    }
Loading

File-Level Changes

Change Details Files
Simplify backend email settings UI and remove SMTP-specific configuration and test email functionality.
  • Remove god-user conditional SMTP configuration fields and mailer type selection from the email settings form action
  • Update the email settings Twig template to only expose from/to/reply-to fields and drop the SMTP panel and test email UI
  • Delete server/port-specific validation and persistence logic tied to SMTP settings
  • Drop unused locale messages and errors related to test emails and SMTP server/port requirements
  • Remove JavaScript handler and rights for the TestEmailConnection action
src/Backend/Modules/Settings/Actions/Email.php
src/Backend/Modules/Settings/Layout/Templates/Email.html.twig
src/Backend/Modules/Settings/Installer/Data/locale.xml
src/Backend/Modules/Settings/Js/Settings.js
src/Backend/Modules/Settings/Installer/Installer.php
tests/data/test_db.sql
src/Backend/Modules/Settings/Ajax/TestEmailConnection.php
Replace SwiftMailer integration with Symfony Mailer and configure it via parameters plus service wiring.
  • Configure Symfony Mailer in the framework configuration using a DSN parameter and remove legacy SwiftMailer config and monolog Swift handlers
  • Remove the custom mailer configurator event listener used to mutate SwiftMailer based on CMS settings
  • Expose Symfony MailerInterface as a public service alias for use in controllers and other services
  • Move mailer DSN management to parameters files instead of CMS-driven SMTP settings
app/config/config.yml
app/config/parameters.yml.dist
app/config/parameters.yml.test
src/Common/Mailer/Configurator.php
Introduce an EmailFactory to create preconfigured TemplatedEmail instances based on stored mailer settings.
  • Add a new EmailFactory service that reads Core mailer_from/mailer_to/mailer_reply_to from ModulesSettings
  • Ensure created TemplatedEmail instances have default from, to, and reply-to Address objects when configured
  • Register EmailFactory as a public service with ModulesSettings injected as a dependency
src/Common/Mailer/EmailFactory.php
app/config/config.yml
Enable Twig extra features and add a custom utm Twig filter for injecting UTM tags into links in HTML content.
  • Require twig/extra-bundle and specific Twig extra packages for CSS inlining and string handling
  • Register TwigExtraBundle in the application kernel so extra Twig features become available
  • Add a UtmExtension Twig extension that defines an utm filter to append UTM parameters to all HTTP(S) href attributes in a given HTML string
  • Register UtmExtension as a Twig extension service
composer.json
app/AppKernel.php
src/Common/Core/Twig/Extensions/UtmExtension.php
app/config/config.yml
Adjust dependencies and tests to align with the new mailer stack.
  • Add Symfony Mailer to Composer dependencies and remove SwiftMailer usage
  • Update test database fixtures to remove rights entries for the removed TestEmailConnection backend action
composer.json
tests/data/test_db.sql

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The UtmExtension::injectUtmTags implementation relies on a simple regex (href="(https?:[^"]+)"), so it will miss links in single-quoted attributes or with unusual formatting; consider using a more robust HTML/DOM-based approach if you expect varied markup.
  • When injecting UTM parameters, existing utm_* query parameters on the URL are not checked or merged, so the filter may append duplicate or conflicting UTM parameters; you may want to detect and preserve/override existing UTM values instead of always appending a new query string.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `UtmExtension::injectUtmTags` implementation relies on a simple regex (`href="(https?:[^"]+)"`), so it will miss links in single-quoted attributes or with unusual formatting; consider using a more robust HTML/DOM-based approach if you expect varied markup.
- When injecting UTM parameters, existing `utm_*` query parameters on the URL are not checked or merged, so the filter may append duplicate or conflicting UTM parameters; you may want to detect and preserve/override existing UTM values instead of always appending a new query string.

## Individual Comments

### Comment 1
<location path="src/Common/Core/Twig/Extensions/UtmExtension.php" line_range="30-46" />
<code_context>
+
+
+        return preg_replace_callback(
+            '/href="(https?:[^"]+)"/i',
+            function (array $matches) use ($campaign, $source, $medium): string {
+                $href = $matches[1];
+
+                $utmParams = http_build_query([
+                    'utm_source' => $source,
+                    'utm_medium' => $medium,
+                    'utm_campaign' => $campaign,
+                ]);
+
+                $separator = str_contains($href, '?') ? '&' : '?';
+
+                return 'href="' . $href . $separator . $utmParams . '"';
+            },
+            $html
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid appending UTM parameters to links that already contain UTM query params to prevent duplication.

This will append `utm_*` params to every `http(s)` href, even when the URL already has UTM tags, leading to duplicates or conflicts (e.g. `?utm_source=a&utm_source=b`). Before appending, either skip URLs where `preg_match('/[?&]utm_(source|medium|campaign)=/i', $href)` matches, or only add missing UTM keys. This will keep URLs consistent and avoid analytics issues.

```suggestion
        return preg_replace_callback(
            '/href="(https?:[^"]+)"/i',
            function (array $matches) use ($campaign, $source, $medium): string {
                $href = $matches[1];

                // Skip if URL already contains any UTM parameter to avoid duplication
                if (preg_match('/[?&]utm_(source|medium|campaign)=/i', $href) === 1) {
                    return $matches[0];
                }

                $utmParams = http_build_query([
                    'utm_source' => $source,
                    'utm_medium' => $medium,
                    'utm_campaign' => $campaign,
                ]);

                $separator = str_contains($href, '?') ? '&' : '?';

                return 'href="' . $href . $separator . $utmParams . '"';
            },
            $html
        );
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +30 to +46
return preg_replace_callback(
'/href="(https?:[^"]+)"/i',
function (array $matches) use ($campaign, $source, $medium): string {
$href = $matches[1];

$utmParams = http_build_query([
'utm_source' => $source,
'utm_medium' => $medium,
'utm_campaign' => $campaign,
]);

$separator = str_contains($href, '?') ? '&' : '?';

return 'href="' . $href . $separator . $utmParams . '"';
},
$html
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Avoid appending UTM parameters to links that already contain UTM query params to prevent duplication.

This will append utm_* params to every http(s) href, even when the URL already has UTM tags, leading to duplicates or conflicts (e.g. ?utm_source=a&utm_source=b). Before appending, either skip URLs where preg_match('/[?&]utm_(source|medium|campaign)=/i', $href) matches, or only add missing UTM keys. This will keep URLs consistent and avoid analytics issues.

Suggested change
return preg_replace_callback(
'/href="(https?:[^"]+)"/i',
function (array $matches) use ($campaign, $source, $medium): string {
$href = $matches[1];
$utmParams = http_build_query([
'utm_source' => $source,
'utm_medium' => $medium,
'utm_campaign' => $campaign,
]);
$separator = str_contains($href, '?') ? '&' : '?';
return 'href="' . $href . $separator . $utmParams . '"';
},
$html
);
return preg_replace_callback(
'/href="(https?:[^"]+)"/i',
function (array $matches) use ($campaign, $source, $medium): string {
$href = $matches[1];
// Skip if URL already contains any UTM parameter to avoid duplication
if (preg_match('/[?&]utm_(source|medium|campaign)=/i', $href) === 1) {
return $matches[0];
}
$utmParams = http_build_query([
'utm_source' => $source,
'utm_medium' => $medium,
'utm_campaign' => $campaign,
]);
$separator = str_contains($href, '?') ? '&' : '?';
return 'href="' . $href . $separator . $utmParams . '"';
},
$html
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants