Skip to content

552 symfony mailer#430

Merged
tijsverkoyen merged 23 commits intomasterfrom
552-symfony-mailer
Mar 16, 2026
Merged

552 symfony mailer#430
tijsverkoyen merged 23 commits intomasterfrom
552-symfony-mailer

Conversation

@tijsverkoyen
Copy link
Copy Markdown
Member

@tijsverkoyen tijsverkoyen commented Feb 27, 2026

Replace swiftmailer/swiftmailer with symfony/mailer

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.
  • Transition all mail-sending code to use Symfony Mailer and TemplatedEmail across frontend and backend modules.
  • Redesign frontend and backend email templates to use Twig Extra filters (inky_to_html, inline_css, custom utm) and shared base layouts, ensuring consistent styling and automatic UTM tagging.
  • Simplify Profiles behavior by removing backend-driven notification emails and display-name change limits, and always treating profile emails as user-initiated actions.

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.

Copy link
Copy Markdown

@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.

@tijsverkoyen
Copy link
Copy Markdown
Member Author

@sourcery-ai review

@tijsverkoyen
Copy link
Copy Markdown
Member Author

@sourcery-ai summary

@tijsverkoyen tijsverkoyen changed the title Draft: 552 symfony mailer 552 symfony mailer Mar 12, 2026
@sumocoders sumocoders deleted a comment from sourcery-ai bot Mar 12, 2026
Copy link
Copy Markdown

@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 5 issues, and left some high level feedback:

  • In the new mail base templates (Frontend/Core/Layout/Templates/Mails/Default.html.twig and the Bootstrap variant) there is an invalid CSS fragment around the .body-drip section (background: #8a8a8a .header .columns { ... }) that is missing a selector and will break that part of the stylesheet; this should be split into proper selectors (e.g. .header { background: #8a8a8a; } .header .columns { ... }).
  • In Faq\Actions\Detail::sendNotificationMail you now call FrontendModel::get(...) but the use Frontend\Core\Engine\Model as FrontendModel; import was removed; either re-add the import or inject the services instead to avoid a runtime error.
  • The new Twig mail templates wrap the content in {% apply inky_to_html|inline_css %} and then apply |inline_css again inside the nested utm apply, which will cause the CSS inliner to run twice on the same markup; consider removing the inner |inline_css to avoid redundant processing.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the new mail base templates (Frontend/Core/Layout/Templates/Mails/Default.html.twig and the Bootstrap variant) there is an invalid CSS fragment around the `.body-drip` section (`background: #8a8a8a .header .columns { ... }`) that is missing a selector and will break that part of the stylesheet; this should be split into proper selectors (e.g. `.header { background: #8a8a8a; } .header .columns { ... }`).
- In Faq\Actions\Detail::sendNotificationMail you now call `FrontendModel::get(...)` but the `use Frontend\Core\Engine\Model as FrontendModel;` import was removed; either re-add the import or inject the services instead to avoid a runtime error.
- The new Twig mail templates wrap the content in `{% apply inky_to_html|inline_css %}` and then apply `|inline_css` again inside the nested `utm` apply, which will cause the CSS inliner to run twice on the same markup; consider removing the inner `|inline_css` to avoid redundant processing.

## Individual Comments

### Comment 1
<location path="src/Frontend/Core/Layout/Templates/Mails/Default.html.twig" line_range="308-309" />
<code_context>
+
+          body, html, .body { background: #ffffff !important; }
+          .container.header { background: #ffffff; }
+          .body-drip { border-top: 8px solid #663399; }
+            background: #8a8a8a .header .columns { padding-bottom: 0; }
+            .header p { color: #fff; padding-top: 15px; }
+            .header .wrapper-inner { padding: 20px; }
</code_context>
<issue_to_address>
**issue (bug_risk):** Malformed CSS rule around the header background will likely be ignored by email clients.

`background: #8a8a8a .header .columns { padding-bottom: 0; }` mixes a declaration and a selector, so it’s invalid CSS and likely ignored. This should be split into two rules, e.g.

```css
.header { background: #8a8a8a; }
.header .columns { padding-bottom: 0; }
```

Otherwise the header styles may not be applied.
</issue_to_address>

### Comment 2
<location path="src/Frontend/Themes/Bootstrap/src/Layout/Templates/Mails/Default.html.twig" line_range="308-309" />
<code_context>
+
+          body, html, .body { background: #ffffff !important; }
+          .container.header { background: #ffffff; }
+          .body-drip { border-top: 8px solid #663399; }
+            background: #8a8a8a .header .columns { padding-bottom: 0; }
+            .header p { color: #fff; padding-top: 15px; }
+            .header .wrapper-inner { padding: 20px; }
</code_context>
<issue_to_address>
**issue (bug_risk):** The same malformed header CSS appears here and should be split into valid rules.

The Bootstrap mail template has the same invalid CSS declaration:

```css
background: #8a8a8a .header .columns { padding-bottom: 0; }
```

Please split this into separate, valid rules, e.g.:

```css
.header { background: #8a8a8a; }
.header .columns { padding-bottom: 0; }
```
so the header styles are parsed and applied correctly.
</issue_to_address>

### Comment 3
<location path="src/Frontend/Core/Layout/Templates/Mails/Default.html.twig" line_range="10-19" />
<code_context>
+                              <th>
+                                {% block main %}
+
+                                {% endblock %}
+                              </th>
+                              <th class="expander"></th>
+                            </tr>
+                          </table>
+                        </th>
+                      </tr>
+                    </tbody>
+                  </table>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+
+          <table class="spacer float-center">
+            <tbody>
+              <tr>
+                <td height="16px" style="font-size:16px;line-height:16px;">&#xA0;</td>
+              </tr>
+            </tbody>
+          </table>
+
+        </center>
+      </td>
+    </tr>
</code_context>
<issue_to_address>
**issue (bug_risk):** There is an unmatched </center> tag inside the logo block that will break the HTML structure.

The `</center>` here has no matching `<center>` within this block, which produces invalid HTML and may break email layouts. Remove this closing tag or wrap the relevant section in a matching `<center>` if centering is required.
</issue_to_address>

### Comment 4
<location path="src/Frontend/Themes/Bootstrap/src/Layout/Templates/Mails/Default.html.twig" line_range="10-19" />
<code_context>
+                              <th>
+                                {% block main %}
+
+                                {% endblock %}
+                              </th>
+                              <th class="expander"></th>
+                            </tr>
+                          </table>
+                        </th>
+                      </tr>
+                    </tbody>
+                  </table>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+
+          <table class="spacer float-center">
+            <tbody>
+              <tr>
+                <td height="16px" style="font-size:16px;line-height:16px;">&#xA0;</td>
+              </tr>
+            </tbody>
+          </table>
+
+        </center>
+      </td>
+    </tr>
</code_context>
<issue_to_address>
**issue (bug_risk):** The Bootstrap mail template also has an extra closing </center> without a matching opening tag.

As in the core template, the `</center>` immediately after `{% endblock logo %}` has no corresponding `<center>` in this block. For HTML emails this mismatch can cause layout issues; please remove it or wrap the relevant content in a matching `<center>...</center>` pair.
</issue_to_address>

### Comment 5
<location path="src/Frontend/Modules/FormBuilder/EventListener/FormBuilderSubmittedMailSubscriber.php" line_range="50-53" />
<code_context>
-     *
-     * @return Swift_Mime_SimpleMessage
-     */
     private function getMessage(
         array $form,
         array $fieldData,
-        ?string $subject = null,
-        $to = null,
-        bool $isConfirmationMail = false
-    ) : Swift_Mime_SimpleMessage {
+        ?string $subject = null
+    ): TemplatedEmail {
         if ($subject === null) {
</code_context>
<issue_to_address>
**issue (bug_risk):** The helper that builds the form submission email no longer sets a recipient, which may send to the default instead of the form-specific address.

`getMessage()` now only sets subject/template/context and never calls `->to()`. Previously it used `setTo($to ?? $form['email'])`, so form submissions defaulted to the form’s configured address.

Unless every caller now explicitly calls `->to(...)`, emails will fall back to the `EmailFactory` default recipient (`mailer_to`), changing behaviour and potentially sending form notifications to the wrong address. Please either (a) reintroduce a `to` parameter (defaulting to `$form['email']`) and call `->to(...)` here, or (b) ensure all call sites explicitly set `->to(...)` after `getMessage()`.
</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.

@sumocoders sumocoders deleted a comment from sourcery-ai bot Mar 12, 2026
@tijsverkoyen
Copy link
Copy Markdown
Member Author

  • In the new mail base templates (Frontend/Core/Layout/Templates/Mails/Default.html.twig and the Bootstrap variant) there is an invalid CSS fragment around the .body-drip section (background: #8a8a8a .header .columns { ... }) that is missing a selector and will break that part of the stylesheet; this should be split into proper selectors (e.g. .header { background: #8a8a8a; } .header .columns { ... }).

Fixed

  • In Faq\Actions\Detail::sendNotificationMail you now call FrontendModel::get(...) but the use Frontend\Core\Engine\Model as FrontendModel; import was removed; either re-add the import or inject the services instead to avoid a runtime error.

Incorrect

  • The new Twig mail templates wrap the content in {% apply inky_to_html|inline_css %} and then apply |inline_css again inside the nested utm apply, which will cause the CSS inliner to run twice on the same markup; consider removing the inner |inline_css to avoid redundant processing.

Fixed in 66b0490

@absumo absumo self-requested a review March 16, 2026 12:42
@tijsverkoyen tijsverkoyen merged commit ff89287 into master Mar 16, 2026
10 of 12 checks passed
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.

3 participants