Index: src/wp-admin/includes/class-plugin-upgrader.php
===================================================================
--- src/wp-admin/includes/class-plugin-upgrader.php	(revision 48381)
+++ src/wp-admin/includes/class-plugin-upgrader.php	(working copy)
@@ -205,6 +205,14 @@
 		// Force refresh of plugin update information.
 		wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 
+		// Ensure any future auto-update failures trigger a failure email when plugins update successfully.
+		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
+
+		if ( isset( $past_failure_emails[ $plugin ] ) ) {
+			unset( $past_failure_emails[ $plugin ] );
+			update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
+		}
+
 		return true;
 	}
 
@@ -329,6 +337,19 @@
 		// Cleanup our hooks, in case something else does a upgrade on this connection.
 		remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
 
+		// Ensure any future auto-update failures trigger a failure email when plugins update successfully.
+		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
+
+		foreach ( $results as $plugin => $result ) {
+			// Maintain last failure notification when plugins failed to update manually.
+			if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $plugin ] ) ) {
+				continue;
+			}
+
+			unset( $past_failure_emails[ $plugin ] );
+		}
+		update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
+
 		return $results;
 	}
 
Index: src/wp-admin/includes/class-theme-upgrader.php
===================================================================
--- src/wp-admin/includes/class-theme-upgrader.php	(revision 48381)
+++ src/wp-admin/includes/class-theme-upgrader.php	(working copy)
@@ -313,6 +313,14 @@
 
 		wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 
+		// Ensure any future auto-update failures trigger a failure email when themes update successfully.
+		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
+
+		if ( isset( $past_failure_emails[ $theme ] ) ) {
+			unset( $past_failure_emails[ $theme ] );
+			update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
+		}
+
 		return true;
 	}
 
@@ -441,6 +449,19 @@
 		remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) );
 		remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) );
 
+		// Ensure any future auto-update failures trigger a failure email when themes update successfully.
+		$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
+
+		foreach ( $results as $theme => $result ) {
+			// Maintain last failure notification when themes failed to update manually.
+			if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $theme ] ) ) {
+				continue;
+			}
+
+			unset( $past_failure_emails[ $theme ] );
+		}
+		update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
+
 		return $results;
 	}
 
Index: src/wp-admin/includes/class-wp-automatic-updater.php
===================================================================
--- src/wp-admin/includes/class-wp-automatic-updater.php	(revision 48381)
+++ src/wp-admin/includes/class-wp-automatic-updater.php	(working copy)
@@ -941,6 +941,32 @@
 			return;
 		}
 
+		$unique_failures = false;
+		$failure_emails  = get_option( 'auto_plugin_theme_update_emails', array() );
+
+		// When only failures have occurred, an email should only be sent if there are unique failures.
+		// A failure is considered unique if an email has not been sent for an update attempt failure
+		// to a plugin or theme with the same new_version.
+		if ( 'fail' === $type ) {
+			foreach ( $failed_updates as $update_type => $failures ) {
+				foreach ( $failures as $failed_update ) {
+					if ( ! isset( $failure_emails[ $failed_update->item->{$update_type} ] ) ) {
+						$unique_failures = true;
+						continue;
+					}
+
+					// Check that the failure represents a new failure based on the new_version.
+					if ( version_compare( $failure_emails[ $failed_update->item->{$update_type} ], $failed_update->item->new_version, '<' ) ) {
+						$unique_failures = true;
+					}
+				}
+			}
+
+			if ( ! $unique_failures ) {
+				return;
+			}
+		}
+
 		$body               = array();
 		$successful_plugins = ( ! empty( $successful_updates['plugin'] ) );
 		$successful_themes  = ( ! empty( $successful_updates['theme'] ) );
@@ -1017,7 +1043,8 @@
 				$body[] = __( 'These plugins failed to update:' );
 
 				foreach ( $failed_updates['plugin'] as $item ) {
-					$body[] = "- {$item->name}";
+					$body[]                                = "- {$item->name}";
+					$failure_emails[ $item->item->plugin ] = $item->item->new_version;
 				}
 				$body[] = "\n";
 			}
@@ -1027,7 +1054,8 @@
 				$body[] = __( 'These themes failed to update:' );
 
 				foreach ( $failed_updates['theme'] as $item ) {
-					$body[] = "- {$item->name}";
+					$body[]                               = "- {$item->name}";
+					$failure_emails[ $item->item->theme ] = $item->item->new_version;
 				}
 				$body[] = "\n";
 			}
@@ -1043,6 +1071,7 @@
 
 				foreach ( $successful_updates['plugin'] as $item ) {
 					$body[] = "- {$item->name}";
+					unset( $failure_emails[ $item->item->plugin ] );
 				}
 				$body[] = "\n";
 			}
@@ -1053,6 +1082,7 @@
 				// List successful updates.
 				foreach ( $successful_updates['theme'] as $item ) {
 					$body[] = "- {$item->name}";
+					unset( $failure_emails[ $item->item->theme ] );
 				}
 				$body[] = "\n";
 			}
@@ -1108,7 +1138,11 @@
 		 */
 		$email = apply_filters( 'auto_plugin_theme_update_email', $email, $type, $successful_updates, $failed_updates );
 
-		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
+		$result = wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
+
+		if ( $result ) {
+			update_option( 'auto_plugin_theme_update_emails', $failure_emails );
+		}
 	}
 
 	/**
Index: src/wp-admin/includes/schema.php
===================================================================
--- src/wp-admin/includes/schema.php	(revision 48381)
+++ src/wp-admin/includes/schema.php	(working copy)
@@ -534,6 +534,7 @@
 		// 5.5.0
 		'blocklist_keys'                  => '',
 		'comment_previously_approved'     => 1,
+		'auto_plugin_theme_update_emails' => array(),
 	);
 
 	// 3.3.0
@@ -552,7 +553,13 @@
 	$options = wp_parse_args( $options, $defaults );
 
 	// Set autoload to no for these options.
-	$fat_options = array( 'moderation_keys', 'recently_edited', 'blocklist_keys', 'uninstall_plugins' );
+	$fat_options = array(
+		'moderation_keys',
+		'recently_edited',
+		'blocklist_keys',
+		'uninstall_plugins',
+		'auto_plugin_theme_update_emails',
+	);
 
 	$keys             = "'" . implode( "', '", array_keys( $options ) ) . "'";
 	$existing_options = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name in ( $keys )" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
Index: src/wp-includes/version.php
===================================================================
--- src/wp-includes/version.php	(revision 48381)
+++ src/wp-includes/version.php	(working copy)
@@ -20,7 +20,7 @@
  *
  * @global int $wp_db_version
  */
-$wp_db_version = 48121;
+$wp_db_version = 48337;
 
 /**
  * Holds the TinyMCE version.
