diff --git a/automatic_updates.module b/automatic_updates.module
index c602db75cf9a40a5d13e96fed820317ea4703ac2..2c5d74c39b419d9aded7c86b6608b736e5390e87 100644
--- a/automatic_updates.module
+++ b/automatic_updates.module
@@ -42,14 +42,29 @@ function automatic_updates_mail(string $key, array &$message, array $params): vo
   $options = [
     'langcode' => $message['langcode'],
   ];
+  switch ($key) {
+    case 'cron_successful':
+      $message['subject'] = t("Drupal core was successfully updated", [], $options);
+      $message['body'][] = t('Congratulations!', [], $options);
+      $message['body'][] = t('Drupal core was automatically updated from @previous_version to @updated_version.', [
+        '@previous_version' => $params['previous_version'],
+        '@updated_version' => $params['updated_version'],
+      ], $options);
+      break;
 
-  if ($key === 'cron_successful') {
-    $message['subject'] = t("Drupal core was successfully updated", [], $options);
-    $message['body'][] = t('Congratulations!', [], $options);
-    $message['body'][] = t('Drupal core was automatically updated from @previous_version to @updated_version.', [
-      '@previous_version' => $params['previous_version'],
-      '@updated_version' => $params['updated_version'],
-    ], $options);
+    case 'cron_failed':
+      $message['subject'] = t("Drupal core update failed", [], $options);
+      $message['body'][] = t('Drupal core failed to update automatically from @previous_version to @target_version. The following error was logged:', [
+        '@previous_version' => $params['previous_version'],
+        '@target_version' => $params['target_version'],
+      ], $options);
+      $message['body'][] = $params['error_message'];
+      break;
+  }
+
+  // If this email was related to an unattended update, explicitly state that
+  // this isn't supported yet.
+  if (str_starts_with($key, 'cron_')) {
     $message['body'][] = t('This e-mail was sent by the Automatic Updates module. Unattended updates are not yet fully supported.', [], $options);
     $message['body'][] = t('If you are using this feature in production, it is strongly recommended for you to visit your site and ensure that everything still looks good.', [], $options);
   }
diff --git a/src/CronUpdater.php b/src/CronUpdater.php
index f79d7561b206fda98916ab5156daaa6323e3b786..dc8711275f7997b5302be4acec08ca5e3dfa287a 100644
--- a/src/CronUpdater.php
+++ b/src/CronUpdater.php
@@ -182,6 +182,15 @@ class CronUpdater extends Updater {
       $this->apply();
     }
     catch (\Throwable $e) {
+      // Send notifications about the failed update.
+      $mail_params = [
+        'previous_version' => $installed_version,
+        'target_version' => $target_version,
+        'error_message' => $e->getMessage(),
+      ];
+      foreach ($this->getEmailRecipients() as $email => $langcode) {
+        $this->mailManager->mail('automatic_updates', 'cron_failed', $email, $langcode, $mail_params);
+      }
       $this->logger->error($e->getMessage());
 
       // If an error occurred during the pre-create event, the stage will be
@@ -276,10 +285,8 @@ class CronUpdater extends Updater {
         'previous_version' => $installed_version,
         'updated_version' => $target_version,
       ];
-      $recipients = $this->configFactory->get('update.settings')
-        ->get('notification.emails');
-      foreach ($recipients as $recipient) {
-        $this->mailManager->mail('automatic_updates', 'cron_successful', $recipient, $this->getEmailLangcode($recipient), $mail_params);
+      foreach ($this->getEmailRecipients() as $recipient => $langcode) {
+        $this->mailManager->mail('automatic_updates', 'cron_successful', $recipient, $langcode, $mail_params);
       }
     }
     catch (\Throwable $e) {
@@ -317,6 +324,23 @@ class CronUpdater extends Updater {
     return $this->languageManager->getDefaultLanguage()->getId();
   }
 
+  /**
+   * Returns an array of people to email with success or failure notifications.
+   *
+   * @return string[]
+   *   An array whose keys are the email addresses to send notifications to, and
+   *   values are the langcodes that they should be emailed in.
+   */
+  protected function getEmailRecipients(): array {
+    $recipients = $this->configFactory->get('update.settings')
+      ->get('notification.emails');
+    $emails = [];
+    foreach ($recipients as $recipient) {
+      $emails[$recipient] = $this->getEmailLangcode($recipient);
+    }
+    return $emails;
+  }
+
   /**
    * Gets the cron update mode.
    *
diff --git a/tests/modules/automatic_updates_test/automatic_updates_test.module b/tests/modules/automatic_updates_test/automatic_updates_test.module
index bd140ddb95a993b7e4547b45fad71232f5e65f5a..2aec23f2382601623f993738994d6c552625ffa7 100644
--- a/tests/modules/automatic_updates_test/automatic_updates_test.module
+++ b/tests/modules/automatic_updates_test/automatic_updates_test.module
@@ -20,11 +20,9 @@ function automatic_updates_test_mail_alter(array &$message): void {
       $message['subject'],
     ]);
     foreach ($lines as $line) {
-      if (!($line instanceof TranslatableMarkup)) {
-        // All lines need to be translated.
-        return;
+      if ($line instanceof TranslatableMarkup) {
+        $line_langcodes[] = $line->getOption('langcode');
       }
-      $line_langcodes[] = $line->getOption('langcode');
     }
     $message['line_langcodes'] = array_unique($line_langcodes);
   }
diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php
index c0e4c0c8c67e0a12210a214610756fb33261c483..9fe45de88a2032545cb9ab43b720db0a82c5e581 100644
--- a/tests/src/Kernel/CronUpdaterTest.php
+++ b/tests/src/Kernel/CronUpdaterTest.php
@@ -52,6 +52,16 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
    */
   private $logger;
 
+  /**
+   * The people who should be emailed about successful or failed updates.
+   *
+   * The keys are the email addresses, and the values are the langcode they
+   * should be emailed in.
+   *
+   * @var string[]
+   */
+  private $emailRecipients = [];
+
   /**
    * {@inheritdoc}
    */
@@ -64,6 +74,26 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
       ->addLogger($this->logger);
     $this->installEntitySchema('user');
     $this->installSchema('user', ['users_data']);
+
+    // Prepare the recipient list to email when an update succeeds or fails.
+    // First, create a user whose preferred language is different from the
+    // default language, so we can be sure they're emailed in their preferred
+    // language; we also ensure that an email which doesn't correspond to a user
+    // account is emailed in the default language.
+    $default_language = $this->container->get('language_manager')
+      ->getDefaultLanguage()
+      ->getId();
+    $this->assertNotSame('fr', $default_language);
+
+    $account = $this->createUser([], NULL, FALSE, [
+      'preferred_langcode' => 'fr',
+    ]);
+    $this->emailRecipients['emissary@deep.space'] = $default_language;
+    $this->emailRecipients[$account->getEmail()] = $account->getPreferredLangcode();
+
+    $this->config('update.settings')
+      ->set('notification.emails', array_keys($this->emailRecipients))
+      ->save();
   }
 
   /**
@@ -349,51 +379,99 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
   }
 
   /**
-   * Tests that user 1 is emailed when an unattended update succeeds.
+   * Tests that email is sent when an unattended update succeeds.
    */
   public function testEmailOnSuccess(): void {
-    $default_language = $this->container->get('language_manager')
-      ->getDefaultLanguage()
-      ->getId();
-    $this->assertNotSame('fr', $default_language);
+    $this->container->get('cron')->run();
 
-    $account = $this->createUser([], NULL, FALSE, [
-      'preferred_langcode' => 'fr',
+    // Ensure we sent a success message to all recipients.
+    $sent_messages = $this->getMails([
+      'subject' => "Drupal core was successfully updated",
     ]);
+    $this->assertNotEmpty($sent_messages);
+    $this->assertSame(count($this->emailRecipients), count($sent_messages));
 
-    $recipients = [
-      'emissary@deep.space',
-      $account->getEmail(),
-    ];
-    $languages = [
-      $default_language,
-      $account->getPreferredLangcode(),
+    foreach ($sent_messages as $message) {
+      $email = $message['to'];
+      $this->assertSame($message['langcode'], $this->emailRecipients[$email]);
+      $this->assertCorrectMessageSent($email, $message, $message['langcode'], "Congratulations!\n\nDrupal core was automatically updated from 9.8.0 to 9.8.1.\n");
+    }
+  }
+
+  /**
+   * Data provider for ::testEmailOnFailure().
+   *
+   * @return string[][]
+   *   The test cases.
+   */
+  public function providerEmailOnFailure(): array {
+    return [
+      'pre-create' => [
+        PreCreateEvent::class,
+      ],
+      'pre-require' => [
+        PreRequireEvent::class,
+      ],
+      'pre-apply' => [
+        PreApplyEvent::class,
+      ],
     ];
+  }
 
-    $this->config('update.settings')
-      ->set('notification.emails', $recipients)
-      ->save();
+  /**
+   * Tests that email is sent when an unattended update fails.
+   *
+   * @param string $event_class
+   *   The event class that should trigger the failure.
+   *
+   * @dataProvider providerEmailOnFailure
+   */
+  public function testEmailOnFailure(string $event_class): void {
+    $results = [
+      ValidationResult::createError(['Error while updating!']),
+    ];
+    TestSubscriber1::setTestResult($results, $event_class);
+    $exception = new StageValidationException($results);
 
     $this->container->get('cron')->run();
 
-    // Ensure we sent a success message to all recipients.
+    // Ensure we sent a failure message to all recipients.
     $sent_messages = $this->getMails([
-      'subject' => "Drupal core was successfully updated",
+      'subject' => "Drupal core update failed",
     ]);
-    $this->assertSame(count($recipients), count($sent_messages));
+    $this->assertNotEmpty($sent_messages);
+    $this->assertSame(count($this->emailRecipients), count($sent_messages));
+
+    foreach ($sent_messages as $message) {
+      $email = $message['to'];
+      $this->assertSame($message['langcode'], $this->emailRecipients[$email]);
+      $this->assertCorrectMessageSent($email, $message, $message['langcode'], "Drupal core failed to update automatically from 9.8.0 to 9.8.1. The following\nerror was logged:\n\n" . $exception->getMessage());
+    }
+  }
+
+  /**
+   * Asserts correct message sent to correct recipient.
+   *
+   * @param string $expected_recipient
+   *   The email address that should have received the message.
+   * @param array $sent_message
+   *   The sent message, as processed by hook_mail().
+   * @param string $expected_language_code
+   *   The language code that the recipient should have been emailed in.
+   * @param string $expected_body_text
+   *   The expected message that the email body should contain.
+   */
+  private function assertCorrectMessageSent(string $expected_recipient, array $sent_message, string $expected_language_code, string $expected_body_text): void {
     // Ensure the messages had the correct body text, and were sent to the right
     // people.
-    foreach ($sent_messages as $i => $message) {
-      $this->assertSame($message['to'], $recipients[$i]);
-      $this->assertStringStartsWith("Congratulations!\n\nDrupal core was automatically updated from 9.8.0 to 9.8.1.\n", $message['body']);
-      // The message, and every line in it, should have been sent in the
-      // expected language.
-      $langcode = $message['langcode'];
-      $this->assertSame($langcode, $languages[$i]);
-      // @see automatic_updates_test_mail_alter()
-      $this->assertArrayHasKey('line_langcodes', $message);
-      $this->assertSame([$langcode], $message['line_langcodes']);
-    }
+    $this->assertSame($sent_message['to'], $expected_recipient);
+    $this->assertStringStartsWith($expected_body_text, $sent_message['body']);
+    // The message, and every line in it, should have been sent in the
+    // expected language.
+    $this->assertSame($expected_language_code, $sent_message['langcode']);
+    // @see automatic_updates_test_mail_alter()
+    $this->assertArrayHasKey('line_langcodes', $sent_message);
+    $this->assertSame([$expected_language_code], $sent_message['line_langcodes']);
   }
 
 }