Issue #3585503: Configurable handling of users with empty or malformed email addresses

Closes #3585503. Addresses the edge case raised in #3521201 comment #15.

Summary

With MR !51 (merged) on #3521201, a user can only be purged once a record in purge_users_notifications exists. Users whose email can't be sent to (sendNotificationEmail() returns FALSE — empty address or the mail backend rejects a malformed address) never get a record, so they stay stuck in the notification queue forever.

This MR adds two checkboxes — both default OFF to preserve current behavior — in the "Notification User Before Deletion" section of both the global settings form and the policy edit form:

  • Skip notification of users with empty emails — when checked, users without an email address are flagged as notified anyway and included in the purge.
  • Also skip notification of users with invalid emails — same for addresses failing Drupal email validation. Only takes effect when the first option is checked.

Runtime

A new helper UserManagementService::shouldAutoFlagUnsendableEmail() resolves which pair of booleans to use:

  • Policy with send_email_user_before_notification === 'enabled' → policy's own booleans.
  • Policy with 'default' → falls back to global settings.
  • Policy with 'disabled' → opts out entirely; returns FALSE (no notification flow runs anyway).
  • No policy → global settings.

Scope

  • UserManagementService::notifyUser() and notifyUserToPurge().
  • Settings form, Policy edit form (with #states gated on notifications being enabled).
  • Policy entity: two new properties, getters, config_export keys.
  • Config schema (purge_users.settings + purge_users.purge_users_policy.*).
  • config/install/purge_users.settings.yml defaults.
  • purge_users_update_10003() for existing installs.
  • 9 kernel tests in UnsendableEmailNotifyTest covering all the default / opt-in / cascade / policy-disabled / valid-email combinations.

Out of scope

  • NotificationRequiredCondition::evaluate() joins purge_users_notifications without filtering on type, while checkNotification() filters type = 'notification_users'. Worth a separate issue.
Edited by Frank Mably

Merge request reports

Loading