diff --git a/comment_notify.migrate.inc b/comment_notify.migrate.inc
index 064ea54..a5e3169 100644
--- a/comment_notify.migrate.inc
+++ b/comment_notify.migrate.inc
@@ -1,5 +1,8 @@
 <?php
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\comment_notify\Hook\CommentNotifyMigrateHooks;
+
 /**
  * @file
  * Migration support for the Comment Notify module.
@@ -65,9 +68,8 @@ class CommentNotifyMigrationHandler extends MigrateDestinationHandler {
 /**
  * Implements hook_migrate_api().
  */
-function comment_notify_migrate_api() {
-  $api = [
-    'api' => 2,
-  ];
-  return $api;
+#[LegacyHook]
+function comment_notify_migrate_api()
+{
+    return \Drupal::service(CommentNotifyMigrateHooks::class)->migrateApi();
 }
diff --git a/comment_notify.module b/comment_notify.module
index bc84e66..916ad30 100644
--- a/comment_notify.module
+++ b/comment_notify.module
@@ -6,7 +6,8 @@
  *
  * It works for anonymous and registered users.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\comment_notify\Hook\CommentNotifyHooks;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\comment\CommentInterface;
 use Drupal\comment_notify\Form\CommentNotifySettings;
@@ -182,23 +183,18 @@ function _comment_notify_submit_comment_form(array &$form, FormStateInterface $f
 /**
  * Implements hook_ENTITY_TYPE_update() for comment.
  */
+#[LegacyHook]
 function comment_notify_comment_update(CommentInterface $comment) {
-  // And send notifications - the real purpose of the module.
-  if ($comment->isPublished()) {
-    _comment_notify_mailalert($comment);
-  }
+  \Drupal::service(CommentNotifyHooks::class)->commentUpdate($comment);
 }
 
 /**
  * Implements hook_comment_insert().
  */
-function comment_notify_comment_insert(CommentInterface $comment) {
-  \Drupal::moduleHandler()->loadInclude('comment_notify', 'inc');
-
-  // And send notifications - the real purpose of the module.
-  if ($comment->isPublished()) {
-    _comment_notify_mailalert($comment);
-  }
+#[LegacyHook]
+function comment_notify_comment_insert(CommentInterface $comment)
+{
+    \Drupal::service(CommentNotifyHooks::class)->commentInsert($comment);
 }
 
 /**
@@ -229,71 +225,10 @@ function _comment_notify_get_comment_enabled_bundles() {
 /**
  * Implements hook_form_alter().
  */
-function comment_notify_form_user_form_alter(&$form, FormStateInterface &$form_state, $form_id) {
-  \Drupal::moduleHandler()->loadInclude('comment_notify', 'inc');
-
-  /** @var \Drupal\user\UserInterface $user */
-  $user = $form_state->getFormObject()->getEntity();
-  /** @var \Drupal\comment_notify\UserNotificationSettings $user_settings */
-  $user_settings = \Drupal::service('comment_notify.user_settings');
-  $notify_settings = $user->id() && $user_settings->getSettings($user->id()) ? $user_settings->getSettings($user->id()) : $user_settings->getDefaultSettings();
-
-  // Only show the entity followup UI if the user has permission to create
-  // entities.
-  $bundles = FALSE;
-  foreach (_comment_notify_get_comment_enabled_bundles() as $entity_type => $bundle) {
-    if (\Drupal::entityTypeManager()->getAccessControlHandler($entity_type)->createAccess($bundle)) {
-      $bundles = TRUE;
-      break;
-    }
-  }
-
-  // If the user cannot create nodes nor has the 'subscribe to comments'
-  // permission then there is no need to alter the user_form.
-  if ((!\Drupal::currentUser()->hasPermission('administer nodes') && $bundles === FALSE) && ($user->hasPermission('subscribe to comments') === FALSE)) {
-    return;
-  }
-
-  $form['comment_notify_settings'] = [
-    '#type' => 'details',
-    '#title' => t('Comment follow-up notification settings'),
-    '#weight' => 4,
-    '#open' => TRUE,
-  ];
-
-  if (\Drupal::currentUser()->hasPermission('administer nodes') || $bundles) {
-    $form['comment_notify_settings']['entity_notify'] = [
-      '#type' => 'checkbox',
-      '#title' => t('Receive content follow-up notification emails'),
-      '#default_value' => isset($notify_settings['entity_notify']) ? $notify_settings['entity_notify'] : NULL,
-      '#description' => t('Check this box to receive email notifications for comments on your <strong>content</strong> (e.g. an article authored by you). You cannot disable notifications for individual threads.'),
-    ];
-  }
-  else {
-    $form['comment_notify_settings']['entity_notify'] = [
-      '#type' => 'hidden',
-      '#value' => COMMENT_NOTIFY_DISABLED,
-    ];
-  }
-
-  if ($user->hasPermission('subscribe to comments')) {
-    $available_options[COMMENT_NOTIFY_DISABLED] = t('No notifications');
-    $available_options += _comment_notify_options();
-    $form['comment_notify_settings']['comment_notify'] = [
-      '#type' => 'select',
-      '#title' => t('Receive comment follow-up notification emails'),
-      '#default_value' => isset($notify_settings['comment_notify']) ? $notify_settings['comment_notify'] : NULL,
-      '#options' => $available_options,
-      '#description' => t("Choose whether, by default, to subscribe to email notifications for replies to your own <strong>comments</strong>. Your site administrator may have customised the options available. You can change this setting on a per-comment basis, and later unsubscribe from individual posts."),
-    ];
-  }
-  else {
-    $form['comment_notify_settings']['comment_notify'] = [
-      '#type' => 'hidden',
-      '#value' => COMMENT_NOTIFY_DISABLED,
-    ];
-  }
-  $form['actions']['submit']['#submit'][] = '_comment_notify_submit_user_form';
+#[LegacyHook]
+function comment_notify_form_user_form_alter(&$form, FormStateInterface &$form_state, $form_id)
+{
+    \Drupal::service(CommentNotifyHooks::class)->formUserFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -323,9 +258,9 @@ function comment_notify_user_predelete(EntityInterface $entity) {
 /**
  * Implements hook_user_cancel().
  */
+#[LegacyHook]
 function comment_notify_user_cancel($edit, $account, $method) {
-  // This hook is invoked when the account is disabled.
-  comment_notify_remove_user_settings($account->id());
+  \Drupal::service(CommentNotifyHooks::class)->userCancel($edit, $account, $method);
 }
 
 /**
@@ -343,27 +278,10 @@ function comment_notify_remove_user_settings($uid) {
 /**
  * Implements hook_comment_load().
  */
-function comment_notify_comment_load($comments) {
-  // Load some comment_notify specific information into the comment object.
-  $query = \Drupal::database()->select('comment_notify', 'cn');
-  $query->join('comment_field_data', 'c', 'c.cid = cn.cid');
-  $query->leftJoin('users_field_data', 'u', 'c.uid = u.uid');
-  $query->condition('c.cid', array_keys($comments), 'IN');
-  $query->fields('cn', ['cid', 'notify', 'notify_hash', 'notified']);
-  $query->addField('c', 'mail', 'cmail');
-  $query->addField('u', 'init', 'uinit');
-  $query->addField('u', 'mail', 'umail');
-
-  $records = $query->execute()->fetchAllAssoc('cid');
-  foreach ($records as $cid => $record) {
-    $comments[$cid]->notify = $record->notify;
-    $comments[$cid]->notify_type = $record->notify;
-    $comments[$cid]->notify_hash = $record->notify_hash;
-    $comments[$cid]->notified = $record->notified;
-    $comments[$cid]->cmail = $record->cmail;
-    $comments[$cid]->uinit = $record->uinit;
-    $comments[$cid]->umail = $record->umail;
-  }
+#[LegacyHook]
+function comment_notify_comment_load($comments)
+{
+    \Drupal::service(CommentNotifyHooks::class)->commentLoad($comments);
 }
 
 /**
@@ -534,9 +452,10 @@ function _comment_notify_mailalert(CommentInterface $comment) {
 /**
  * Implements hook_mail().
  */
-function comment_notify_mail($key, &$message, $params) {
-  $message['subject'] = $params['subject'];
-  $message['body'][] = $params['body'];
+#[LegacyHook]
+function comment_notify_mail($key, &$message, $params)
+{
+    \Drupal::service(CommentNotifyHooks::class)->mail($key, $message, $params);
 }
 
 /**
@@ -595,21 +514,8 @@ function comment_notify_entity_extra_field_info() {
 /**
  * Implements hook_help().
  */
-function comment_notify_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.comment_notify':
-      $text = file_get_contents(dirname(__FILE__) . '/README.md');
-      if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
-        return '<pre>' . $text . '</pre>';
-      }
-      else {
-        // Use the Markdown filter to render the README.
-        $filter_manager = \Drupal::service('plugin.manager.filter');
-        $settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
-        $config = ['settings' => $settings];
-        $filter = $filter_manager->createInstance('markdown', $config);
-        return $filter->process($text, 'en');
-      }
-  }
-  return NULL;
+#[LegacyHook]
+function comment_notify_help($route_name, RouteMatchInterface $route_match)
+{
+    return \Drupal::service(CommentNotifyHooks::class)->help($route_name, $route_match);
 }
diff --git a/comment_notify.services.yml b/comment_notify.services.yml
index 18e3aa4..e76a8ec 100644
--- a/comment_notify.services.yml
+++ b/comment_notify.services.yml
@@ -2,3 +2,15 @@ services:
   comment_notify.user_settings:
     class: Drupal\comment_notify\UserNotificationSettings
     arguments: ['@user.data', '@config.factory']
+
+  Drupal\comment_notify\Hook\CommentNotifyMigrateHooks:
+    class: Drupal\comment_notify\Hook\CommentNotifyMigrateHooks
+    autowire: true
+
+  Drupal\comment_notify\Hook\CommentNotifyHooks:
+    class: Drupal\comment_notify\Hook\CommentNotifyHooks
+    autowire: true
+
+  Drupal\comment_notify\Hook\CommentNotifyTokensHooks:
+    class: Drupal\comment_notify\Hook\CommentNotifyTokensHooks
+    autowire: true
diff --git a/comment_notify.tokens.inc b/comment_notify.tokens.inc
index 1f2808a..f0e7c8f 100644
--- a/comment_notify.tokens.inc
+++ b/comment_notify.tokens.inc
@@ -4,69 +4,24 @@
  * @file
  * Builds placeholder replacement tokens for comment_notify.module.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\comment_notify\Hook\CommentNotifyTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
  * Implements hook_token_info().
  */
-function comment_notify_token_info() {
-  // Comment tokens.
-  $info['tokens']['comment']['unsubscribe-url'] = [
-    'name' => t('Unsubscribe URL'),
-    'description' => t('The URL to disable notifications for the comment.'),
-    'type' => 'url',
-  ];
-
-  // Comment subscriber token type (extends the comment token type).
-  $info['types']['comment-subscribed'] = [
-    'name' => t('Subscribed comment'),
-    'description' => t('Tokens related to a comment that is subscribed to new comments.'),
-    'type' => 'comment',
-  ];
-
-  return $info;
+#[LegacyHook]
+function comment_notify_token_info()
+{
+    return \Drupal::service(CommentNotifyTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
-function comment_notify_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
-  $url_options = ['absolute' => TRUE];
-  if (isset($options['language'])) {
-    $url_options['language'] = $options['language'];
-    $language_code = $options['language']->language;
-  }
-  else {
-    $language_code = NULL;
-  }
-  $sanitize = !empty($options['sanitize']);
-
-  $replacements = [];
-
-  if ($type == 'comment' && !empty($data['comment'])) {
-    $comment = $data['comment'];
-
-    foreach ($tokens as $name => $original) {
-      switch ($name) {
-        case 'unsubscribe-url':
-          if ($unsubscribe_url = comment_notify_get_unsubscribe_url($comment)) {
-            $replacements[$original] = $unsubscribe_url;
-          }
-          break;
-      }
-    }
-
-    // [comment:unsubscribe-url:*] chained token replacements.
-    if (($unsubscribe_url_tokens = \Drupal::token()->findWithPrefix($tokens, 'unsubscribe-url')) && $unsubscribe_url = comment_notify_get_unsubscribe_url($comment)) {
-      $replacements += \Drupal::token()->generate('url', $unsubscribe_url_tokens, ['path' => $unsubscribe_url], $options, $bubbleable_metadata);
-    }
-  }
-
-  // Comment subscriber tokens (pass through to comment token replacement).
-  if ($type == 'comment-subscribed' && !empty($data['comment-subscribed'])) {
-    $replacements += \Drupal::token()->generate('comment', $tokens, ['comment' => $data['comment-subscribed']], $options, $bubbleable_metadata);
-  }
-
-  return $replacements;
+#[LegacyHook]
+function comment_notify_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata)
+{
+    return \Drupal::service(CommentNotifyTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
diff --git a/src/Form/CommentNotifySettings.php b/src/Form/CommentNotifySettings.php
index 16efd74..98ab753 100644
--- a/src/Form/CommentNotifySettings.php
+++ b/src/Form/CommentNotifySettings.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\comment_notify\Form;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\comment\AnonymousContact;
 use Drupal\comment\CommentInterface;
 use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Component\Utility\Html;
@@ -105,7 +107,7 @@ class CommentNotifySettings extends ConfigFormBase {
       $entity_types[$entity_type][] = $comment_field_identifier;
       $comment_field = FieldConfig::loadByName($entity_type, $entity_bundle, $field_name);
 
-      if (in_array($entity_type . '--' . $entity_bundle . '--' . $field_name, $enabled_bundles) && $comment_field && $comment_field->getSetting('anonymous') == CommentInterface::ANONYMOUS_MAYNOT_CONTACT) {
+      if (in_array($entity_type . '--' . $entity_bundle . '--' . $field_name, $enabled_bundles) && $comment_field && $comment_field->getSetting('anonymous') == DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => AnonymousContact::Forbidden, fn() => CommentInterface::ANONYMOUS_MAYNOT_CONTACT)) {
         if (User::getAnonymousUser()->hasPermission('subscribe to comments')) {
           // Provide a link if the field_ui module is installed.
           if ($this->moduleHandler->moduleExists('field_ui')) {
diff --git a/src/Hook/CommentNotifyHooks.php b/src/Hook/CommentNotifyHooks.php
new file mode 100644
index 0000000..01b6cd5
--- /dev/null
+++ b/src/Hook/CommentNotifyHooks.php
@@ -0,0 +1,183 @@
+<?php
+
+namespace Drupal\comment_notify\Hook;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\comment\CommentInterface;
+use Drupal\comment_notify\Form\CommentNotifySettings;
+use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Url;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\user\Entity\User;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for comment_notify.
+ */
+class CommentNotifyHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_ENTITY_TYPE_update() for comment.
+     */
+    #[Hook('comment_update')]
+    public static function commentUpdate(\Drupal\comment\CommentInterface $comment)
+    {
+        // And send notifications - the real purpose of the module.
+        if ($comment->isPublished()) {
+            _comment_notify_mailalert($comment);
+        }
+    }
+    /**
+     * Implements hook_comment_insert().
+     */
+    #[Hook('comment_insert')]
+    public static function commentInsert(\Drupal\comment\CommentInterface $comment)
+    {
+        \Drupal::moduleHandler()->loadInclude('comment_notify', 'inc');
+        // And send notifications - the real purpose of the module.
+        if ($comment->isPublished()) {
+            _comment_notify_mailalert($comment);
+        }
+    }
+    /**
+     * Implements hook_form_alter().
+     */
+    #[Hook('form_alter', module: 'comment_notify_form_user')]
+    public function formUserFormAlter(&$form, \Drupal\Core\Form\FormStateInterface &$form_state, $form_id)
+    {
+        \Drupal::moduleHandler()->loadInclude('comment_notify', 'inc');
+        /** @var \Drupal\user\UserInterface $user */
+        $user = $form_state->getFormObject()->getEntity();
+        /** @var \Drupal\comment_notify\UserNotificationSettings $user_settings */
+        $user_settings = \Drupal::service('comment_notify.user_settings');
+        $notify_settings = $user->id() && $user_settings->getSettings($user->id()) ? $user_settings->getSettings($user->id()) : $user_settings->getDefaultSettings();
+        // Only show the entity followup UI if the user has permission to create
+        // entities.
+        $bundles = FALSE;
+        foreach (_comment_notify_get_comment_enabled_bundles() as $entity_type => $bundle) {
+            if (\Drupal::entityTypeManager()->getAccessControlHandler($entity_type)->createAccess($bundle)) {
+                $bundles = TRUE;
+                break;
+            }
+        }
+        // If the user cannot create nodes nor has the 'subscribe to comments'
+        // permission then there is no need to alter the user_form.
+        if (!\Drupal::currentUser()->hasPermission('administer nodes') && $bundles === FALSE && $user->hasPermission('subscribe to comments') === FALSE) {
+            return;
+        }
+        $form['comment_notify_settings'] = [
+            '#type' => 'details',
+            '#title' => $this->t('Comment follow-up notification settings'),
+            '#weight' => 4,
+            '#open' => TRUE,
+        ];
+        if (\Drupal::currentUser()->hasPermission('administer nodes') || $bundles) {
+            $form['comment_notify_settings']['entity_notify'] = [
+                '#type' => 'checkbox',
+                '#title' => $this->t('Receive content follow-up notification emails'),
+                '#default_value' => isset($notify_settings['entity_notify']) ? $notify_settings['entity_notify'] : NULL,
+                '#description' => $this->t('Check this box to receive email notifications for comments on your <strong>content</strong> (e.g. an article authored by you). You cannot disable notifications for individual threads.'),
+            ];
+        } else {
+            $form['comment_notify_settings']['entity_notify'] = [
+                '#type' => 'hidden',
+                '#value' => COMMENT_NOTIFY_DISABLED,
+            ];
+        }
+        if ($user->hasPermission('subscribe to comments')) {
+            $available_options[COMMENT_NOTIFY_DISABLED] = $this->t('No notifications');
+            $available_options += _comment_notify_options();
+            $form['comment_notify_settings']['comment_notify'] = [
+                '#type' => 'select',
+                '#title' => $this->t('Receive comment follow-up notification emails'),
+                '#default_value' => isset($notify_settings['comment_notify']) ? $notify_settings['comment_notify'] : NULL,
+                '#options' => $available_options,
+                '#description' => $this->t("Choose whether, by default, to subscribe to email notifications for replies to your own <strong>comments</strong>. Your site administrator may have customised the options available. You can change this setting on a per-comment basis, and later unsubscribe from individual posts."),
+            ];
+        } else {
+            $form['comment_notify_settings']['comment_notify'] = [
+                '#type' => 'hidden',
+                '#value' => COMMENT_NOTIFY_DISABLED,
+            ];
+        }
+        $form['actions']['submit']['#submit'][] = '_comment_notify_submit_user_form';
+    }
+    /**
+     * Implements hook_user_cancel().
+     */
+    #[Hook('user_cancel')]
+    public static function userCancel($edit, $account, $method)
+    {
+        // This hook is invoked when the account is disabled.
+        comment_notify_remove_user_settings($account->id());
+    }
+    /**
+     * Implements hook_comment_load().
+     */
+    #[Hook('comment_load')]
+    public static function commentLoad($comments)
+    {
+        // Load some comment_notify specific information into the comment object.
+        $query = \Drupal::database()->select('comment_notify', 'cn');
+        $query->join('comment_field_data', 'c', 'c.cid = cn.cid');
+        $query->leftJoin('users_field_data', 'u', 'c.uid = u.uid');
+        $query->condition('c.cid', array_keys($comments), 'IN');
+        $query->fields('cn', [
+            'cid',
+            'notify',
+            'notify_hash',
+            'notified',
+        ]);
+        $query->addField('c', 'mail', 'cmail');
+        $query->addField('u', 'init', 'uinit');
+        $query->addField('u', 'mail', 'umail');
+        $records = $query->execute()->fetchAllAssoc('cid');
+        foreach ($records as $cid => $record) {
+            $comments[$cid]->notify = $record->notify;
+            $comments[$cid]->notify_type = $record->notify;
+            $comments[$cid]->notify_hash = $record->notify_hash;
+            $comments[$cid]->notified = $record->notified;
+            $comments[$cid]->cmail = $record->cmail;
+            $comments[$cid]->uinit = $record->uinit;
+            $comments[$cid]->umail = $record->umail;
+        }
+    }
+    /**
+     * Implements hook_mail().
+     */
+    #[Hook('mail')]
+    public static function mail($key, &$message, $params)
+    {
+        $message['subject'] = $params['subject'];
+        $message['body'][] = $params['body'];
+    }
+    /**
+     * Implements hook_help().
+     */
+    #[Hook('help')]
+    public static function help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match)
+    {
+        switch ($route_name) {
+            case 'help.page.comment_notify':
+                $text = file_get_contents(dirname(__FILE__) . '/README.md');
+                if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
+                    return '<pre>' . $text . '</pre>';
+                } else {
+                    // Use the Markdown filter to render the README.
+                    $filter_manager = \Drupal::service('plugin.manager.filter');
+                    $settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
+                    $config = [
+                        'settings' => $settings,
+                    ];
+                    $filter = $filter_manager->createInstance('markdown', $config);
+                    return $filter->process($text, 'en');
+                }
+        }
+        return NULL;
+    }
+}
diff --git a/src/Hook/CommentNotifyMigrateHooks.php b/src/Hook/CommentNotifyMigrateHooks.php
new file mode 100644
index 0000000..0aad491
--- /dev/null
+++ b/src/Hook/CommentNotifyMigrateHooks.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\comment_notify\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+/**
+ * Hook implementations for comment_notify.
+ */
+class CommentNotifyMigrateHooks
+{
+    /**
+     * Implements hook_migrate_api().
+     */
+    #[Hook('migrate_api')]
+    public static function migrateApi()
+    {
+        $api = [
+            'api' => 2,
+        ];
+        return $api;
+    }
+}
diff --git a/src/Hook/CommentNotifyTokensHooks.php b/src/Hook/CommentNotifyTokensHooks.php
new file mode 100644
index 0000000..8560832
--- /dev/null
+++ b/src/Hook/CommentNotifyTokensHooks.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\comment_notify\Hook;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for comment_notify.
+ */
+class CommentNotifyTokensHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_token_info().
+     */
+    #[Hook('token_info')]
+    public function tokenInfo()
+    {
+        // Comment tokens.
+        $info['tokens']['comment']['unsubscribe-url'] = [
+            'name' => $this->t('Unsubscribe URL'),
+            'description' => $this->t('The URL to disable notifications for the comment.'),
+            'type' => 'url',
+        ];
+        // Comment subscriber token type (extends the comment token type).
+        $info['types']['comment-subscribed'] = [
+            'name' => $this->t('Subscribed comment'),
+            'description' => $this->t('Tokens related to a comment that is subscribed to new comments.'),
+            'type' => 'comment',
+        ];
+        return $info;
+    }
+    /**
+     * Implements hook_tokens().
+     */
+    #[Hook('tokens')]
+    public static function tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata)
+    {
+        $url_options = [
+            'absolute' => TRUE,
+        ];
+        if (isset($options['language'])) {
+            $url_options['language'] = $options['language'];
+            $language_code = $options['language']->language;
+        } else {
+            $language_code = NULL;
+        }
+        $sanitize = !empty($options['sanitize']);
+        $replacements = [
+        ];
+        if ($type == 'comment' && !empty($data['comment'])) {
+            $comment = $data['comment'];
+            foreach ($tokens as $name => $original) {
+                switch ($name) {
+                    case 'unsubscribe-url':
+                        if ($unsubscribe_url = comment_notify_get_unsubscribe_url($comment)) {
+                            $replacements[$original] = $unsubscribe_url;
+                        }
+                        break;
+                }
+            }
+            // [comment:unsubscribe-url:*] chained token replacements.
+            if (($unsubscribe_url_tokens = \Drupal::token()->findWithPrefix($tokens, 'unsubscribe-url')) && $unsubscribe_url = comment_notify_get_unsubscribe_url($comment)) {
+                $replacements += \Drupal::token()->generate('url', $unsubscribe_url_tokens, [
+                    'path' => $unsubscribe_url,
+                ], $options, $bubbleable_metadata);
+            }
+        }
+        // Comment subscriber tokens (pass through to comment token replacement).
+        if ($type == 'comment-subscribed' && !empty($data['comment-subscribed'])) {
+            $replacements += \Drupal::token()->generate('comment', $tokens, [
+                'comment' => $data['comment-subscribed'],
+            ], $options, $bubbleable_metadata);
+        }
+        return $replacements;
+    }
+}
diff --git a/tests/src/Functional/CommentNotifyConfigPageTest.php b/tests/src/Functional/CommentNotifyConfigPageTest.php
index 46f1ade..479961f 100644
--- a/tests/src/Functional/CommentNotifyConfigPageTest.php
+++ b/tests/src/Functional/CommentNotifyConfigPageTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\comment_notify\Functional;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\comment\AnonymousContact;
 use Drupal\comment\CommentInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\field\Entity\FieldConfig;
@@ -168,7 +170,7 @@ class CommentNotifyConfigPageTest extends CommentNotifyTestBase {
     // Tests that a warning error is displayed when anonymous users haven't
     // permission to leave their contact information.
     $comment_field = FieldConfig::loadByName('node', 'article', 'comment');
-    $comment_field->setSetting('anonymous', CommentInterface::ANONYMOUS_MAYNOT_CONTACT);
+    $comment_field->setSetting('anonymous', DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => AnonymousContact::Forbidden, fn() => CommentInterface::ANONYMOUS_MAYNOT_CONTACT));
     $comment_field->save();
     $this->drupalGet("admin/config/people/comment_notify");
     $this->assertSession()->responseContains('Anonymous commenters have the permission to subscribe to comments but they need to be allowed to:');
diff --git a/tests/src/Functional/CommentNotifyNotificationsTest.php b/tests/src/Functional/CommentNotifyNotificationsTest.php
index 194371e..821bffb 100644
--- a/tests/src/Functional/CommentNotifyNotificationsTest.php
+++ b/tests/src/Functional/CommentNotifyNotificationsTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\comment_notify\Functional;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\comment\CommentingStatus;
+use Drupal\comment\AnonymousContact;
 use Drupal\comment\CommentInterface;
 use Drupal\comment\Entity\Comment;
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
@@ -85,7 +88,7 @@ class CommentNotifyNotificationsTest extends CommentNotifyTestBase {
    */
   public function testCommentTypeNotification() {
     // Add a second comment type.
-    $this->addDefaultCommentField('node', 'article', 'field_comment', CommentItemInterface::OPEN, 'comment_type_2');
+    $this->addDefaultCommentField('node', 'article', 'field_comment', DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => CommentingStatus::Open, fn() => CommentItemInterface::OPEN), 'comment_type_2');
     /** @var \Drupal\Core\Config\Config $config */
     $config = $this->container->get('config.factory')->getEditable('comment_notify.settings');
     $config->set('bundle_types', ['node--article--comment', 'node--article--field_comment']);
@@ -174,9 +177,9 @@ class CommentNotifyNotificationsTest extends CommentNotifyTestBase {
   public function testEntityNotification() {
     /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
     $vocabulary = $this->createVocabulary();
-    $this->addDefaultCommentField('taxonomy_term', $vocabulary->id(), 'field_comment_taxonomy', CommentItemInterface::OPEN, 'comment_type_2');
+    $this->addDefaultCommentField('taxonomy_term', $vocabulary->id(), 'field_comment_taxonomy', DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => CommentingStatus::Open, fn() => CommentItemInterface::OPEN), 'comment_type_2');
     $comment_field = FieldConfig::loadByName('taxonomy_term', $vocabulary->id(), 'field_comment_taxonomy');
-    $comment_field->setSetting('anonymous', CommentInterface::ANONYMOUS_MAY_CONTACT);
+    $comment_field->setSetting('anonymous', DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => AnonymousContact::Allowed, fn() => CommentInterface::ANONYMOUS_MAY_CONTACT));
     $comment_field->save();
 
     /** @var \Drupal\Core\Config\Config $config */
diff --git a/tests/src/Functional/CommentNotifyTestBase.php b/tests/src/Functional/CommentNotifyTestBase.php
index de4acdf..017ad39 100644
--- a/tests/src/Functional/CommentNotifyTestBase.php
+++ b/tests/src/Functional/CommentNotifyTestBase.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\comment_notify\Functional;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\comment\AnonymousContact;
 use Drupal\comment\Tests\CommentTestTrait;
 use Drupal\comment\CommentInterface;
 use Drupal\Core\Test\AssertMailTrait;
@@ -62,7 +64,7 @@ abstract class CommentNotifyTestBase extends BrowserTestBase {
     ]);
     $this->addDefaultCommentField('node', 'article');
     $comment_field = FieldConfig::loadByName('node', 'article', 'comment');
-    $comment_field->setSetting('anonymous', CommentInterface::ANONYMOUS_MAY_CONTACT);
+    $comment_field->setSetting('anonymous', DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => AnonymousContact::Allowed, fn() => CommentInterface::ANONYMOUS_MAY_CONTACT));
     $comment_field->save();
   }
 
