Update Email Customizer provides an administration interface for customizing Drupal update notification emails while keeping Drupal's existing update notification workflow and mail delivery system.

The module allows administrators to customize the HTML content and presentation of update notification emails for Drupal core, contributed modules, and themes. It includes a default email template, replacement tokens, security update awareness, and test email functionality from both the administration interface and Drush.

Project link

https://www.drupal.org/project/update_email_customizer

Comments

mxr10 created an issue. See original summary.

avpaderno’s picture

Title: [10.x, 11.x] Update Email Customizer » [1.0.x] Update Email Customizer
Issue summary: View changes

Thank you for applying!

Before giving links helpful to understand how the review process works, what to expect from a review, and what to do to avoid a review takes more time than needed, I would like to thank all the reviewers for the work they do.
These applications are volunters-driven, which also means it is not possible to predict when an application will be marked fixed and the applicant will get the permission to opt projects into security advisory policy. While we aim to make an application as quick as possible, it is also important for us that more people review the project used for an application. In this way, we make sure applications do not miss some important points that should be instead reported.
Applications are not meant to be complete debugging sessions that eliminate every existing bug, though. I apologize if sometimes applications seem to go into too-detailed reviews.

Please read Review process for security advisory coverage: What to expect for more details and Security advisory coverage application checklist to understand what reviewers look for. Tips for ensuring a smooth review gives some hints for a smoother review.

The important notes are the following.

  • If you have not done it yet, you should enable GitLab CI for the project and fix the PHP_CodeSniffer errors/warnings it reports.
  • For the time this application is open, only your commits are allowed.
  • The purpose of this application is giving you a new drupal.org role that allows you to opt projects into security advisory coverage, either projects you already created, or projects you will create. The project status will not be changed by this application; once this application is closed, you will be able to change the project status from Not covered to Opt into security advisory coverage. This is possible only 14 days after the project is created.

    Keep in mind that once the project is opted into security advisory coverage, only Security Team members may change coverage.
  • Only the person who created the application will get the permission to opt projects into security advisory coverage. No other person will get the same permission from the same application; that applies also to co-maintainers/maintainers of the project used for the application.
  • We only accept an application per user. If you change your mind about the project to use for this application, or it is necessary to use a different project for the application, please update the issue summary with the link to the correct project and the issue title with the project name and the branch to review.

To the reviewers

Please read How to review security advisory coverage applications, Application workflow, What to cover in an application review, and Tools to use for reviews.

The important notes are the following.

  • It is preferable to wait for a project moderator before posting the first comment on newly created applications. Project moderators will do some preliminary checks that are necessary before any change on the project files is suggested.
  • Reviewers should show the output of a CLI tool only once per application.
  • It may be best to have the applicant fix things before further review.

For new reviewers, I would also suggest to first read In which way the issue queue for coverage applications is different from other project queues.

vishal.kadam’s picture

Status: Needs review » Needs work

FILE: update_email_customizer.module

For a new module that aims to be compatible with Drupal 10 and Drupal 11, I would rather implement hooks as class methods as described in Support for object oriented hook implementations using autowired services.
It would require increasing the minimum Drupal 10 version supported, but Drupal 10.1 is no longer supported.

mxr10’s picture

Thanks for the review.

I converted the module hooks to object-oriented hook implementations using autowired services.

I also added legacy hook compatibility for Drupal 10 and updated the minimum supported versions to Drupal 10.3 and Drupal 11.1.

The changes are available in commit:
6cbd44e - Convert hooks to object-oriented implementations

The GitLab CI pipeline passes successfully.

avpaderno’s picture

@mxr Remember to change status, if you are done with changing the project files.

mxr10’s picture

Status: Needs work » Needs review
pedroromán’s picture

Status: Needs review » Needs work

Reviewed 1.0.x at 6cbd44e.

src/Hook/UpdateEmailCustomizerHooks.php

    $form['update_email_customizer']['send_test_email'] = [
      '#type' => 'submit',
      '#value' => $this->t('Send test email'),
      '#submit' => [
        [$this, 'sendTestEmailSubmit'],
      ],
    ...
    $form['#submit'][] = [$this, 'saveSettingsSubmit'];

The callbacks put the hook service object itself into the form array. Whenever that form is cached (form cache, AJAX, a rebuild after a validation error) Drupal serializes the array, and this object carries the config factory and the Twig environment, which cannot be serialized. Form callbacks need to be static or procedural; the services can be fetched inside them.

      '#default_value' => $saved_template !== ''
        ? $saved_template
        : $this->getStarterTemplate(),

When nothing is saved yet, the textarea is filled with the starter template, and saveSettingsSubmit() stores whatever the textarea holds. Saving the Update settings form for any other reason (for example the check frequency) silently turns the starter text into a custom template, and from then on the Twig default template is never used. Leave the field empty and show the starter as a #placeholder or in the description instead.

  private function t(
    string $string,
    array $args = [],
    array $options = [],
  ): string {

The class re-implements t() instead of using StringTranslationTrait, and then casts every result with (string) even though the method already returns a string. Use the trait and drop the casts; keeping the TranslatableMarkup objects is also what the render system expects.

update_email_customizer.services.yml

parameters:
  update_email_customizer.skip_procedural_hook_scan: true

That parameter is for modules that have no procedural hooks. This module keeps the #[LegacyHook] functions in the .module file for Drupal 10, so the parameter is not needed; the attribute already tells Drupal 11 to skip them.

README.md

The paragraph starting with "The module uses object-oriented hook implementations" appears twice. The Maintainers section has no maintainer; add your name and drupal.org profile link. The License section is not needed, drupal.org projects are GPL-2.0-or-later by definition.

Tests

The only test covers TestEmailSender. The behaviour of the module is in mailAlter(): subject, headers and the token replacement in a custom template. A kernel test invoking hook_mail_alter() with an update_status_notify message would cover it.

AI-Generated: Yes (AI tools helped with this review; I checked each point myself.)

mxr10’s picture

Status: Needs work » Needs review

Thanks for the review.

I addressed the reported issues:

- Changed the form submit callbacks so the hook service object is no longer stored in the form array.
- Changed the starter template behavior so it is displayed as a placeholder when no custom template is saved, preventing it from being saved unintentionally.
- Replaced the custom translation helper with StringTranslationTrait and removed the unnecessary string casts.
- Removed the unnecessary skip_procedural_hook_scan parameter.
- Cleaned up the README, removed the duplicated paragraph and unnecessary License section, and added the maintainer information.
- Added a Kernel test covering mailAlter(), including the subject, headers, and token replacement with a custom template.

I also verified the module manually on Drupal 10.6.13:
- Configuration can be saved successfully.
- Custom templates are saved and restored correctly.
- The starter template is used when no custom template is configured.
- Test emails work from both the administration interface and Drush.

Tests:
- Kernel test: 1 test, 10 assertions - passed.
- Unit tests: 3 tests, 8 assertions - passed.
- GitLab CI pipeline #961521 passes successfully.

Changes are available in commit 498b5e3f.

Thanks again for the feedback.

pedroromán’s picture

Two corrections to my review in #7.

update_email_customizer.services.yml: please ignore the point about update_email_customizer.skip_procedural_hook_scan. Setting it is correct for a module whose remaining procedural functions are #[LegacyHook] bridges; core does the same in modules that keep a .module file.

src/Hook/UpdateEmailCustomizerHooks.php: the form is not cached after a plain validation error, as I wrote. The serialization problem with [$this, 'method'] callbacks applies when the form is cached (AJAX or a rebuild), which is why static callbacks are still the safer choice.

AI-Generated: Yes (AI tools helped with this review and with this correction.)

mxr10’s picture

Thanks for the clarification.

I restored `update_email_customizer.skip_procedural_hook_scan: true` as the remaining procedural hooks are `#[LegacyHook]` bridges.

The static form callbacks remain unchanged.

The change is available in commit 7ac7bb93, and GitLab CI pipeline #966154 passes successfully.

Thanks again for the review.