Index: src/wp-admin/includes/class-plugin-upgrader.php
===================================================================
--- src/wp-admin/includes/class-plugin-upgrader.php	(revision 47180)
+++ src/wp-admin/includes/class-plugin-upgrader.php	(working copy)
@@ -73,6 +73,36 @@
 	}
 
 	/**
+	 * Determines if this plugin should update to an offered version.
+	 *
+	 * @since 5.4.0
+	 *
+	 * @param object $plugin
+	 * @return bool True if we should update to the offered version, otherwise false.
+	 */
+	public static function should_update_to_version( $plugin ) {
+		if ( defined( 'WP_AUTO_UPDATE_PLUGINS' ) ) {
+			$update = (bool) WP_AUTO_UPDATE_PLUGINS;
+		} else {
+			$enabled_plugin_auto_updates = get_site_option( 'wp_plugin_auto_update', array() );
+			$update                      = in_array( $plugin->plugin, $enabled_plugin_auto_updates, true );
+		}
+
+		/**
+		 * Filters whether to enable automatic updates for the plugin.
+		 *
+		 * @since 5.4.0
+		 *
+		 * @param bool   $update      Whether to enable automatic updates for this plugin.
+		 * @param string $plugin_file Main plugin file.
+		 * @param object $plugin      Plugin object.
+		 */
+		$update = apply_filters( 'allow_auto_plugin_update', $update, $plugin->plugin, $plugin );
+
+		return $update;
+	}
+
+	/**
 	 * Install a plugin package.
 	 *
 	 * @since 2.8.0
Index: src/wp-admin/includes/class-wp-automatic-updater.php
===================================================================
--- src/wp-admin/includes/class-wp-automatic-updater.php	(revision 47180)
+++ src/wp-admin/includes/class-wp-automatic-updater.php	(working copy)
@@ -158,6 +158,8 @@
 		// Next up, is this an item we can update?
 		if ( 'core' == $type ) {
 			$update = Core_Upgrader::should_update_to_version( $item->current );
+		} elseif ( 'plugin' === $type ) {
+			$update = Plugin_Upgrader::should_update_to_version( $item );
 		} else {
 			$update = ! empty( $item->autoupdate );
 		}
@@ -499,6 +501,10 @@
 				$this->after_core_update( $this->update_results['core'][0] );
 			}
 
+			if ( ! empty( $this->update_results['plugin'] ) ) {
+				$this->after_plugin_update( $this->update_results['plugin'] );
+			}
+
 			/**
 			 * Fires after all automatic updates have run.
 			 *
@@ -606,6 +612,43 @@
 	}
 
 	/**
+	 * If we tried to perform plugin updates, check if we should send an email.
+	 *
+	 * @since 5.4.0
+	 *
+	 * @param object $update_results The result of the plugin updates.
+	 */
+	function after_plugin_update( $update_results ) {
+		if ( empty( $update_results ) ) {
+			return;
+		}
+
+		$success_items = array();
+		$failure_items = array();
+
+		foreach ( $update_results as $update_result ) {
+			if ( true === $update_result->result ) {
+				$success_items[] = $update_result;
+			} else {
+				$failure_items[] = $update_result;
+			}
+		}
+
+		// No updates were logged.
+		if ( empty( $success_items ) && empty( $failure_items ) ) {
+			return;
+		}
+
+		if ( empty( $failure_items ) ) {
+			$this->send_plugin_email( 'success', $success_items, $failure_items );
+		} elseif ( empty( $success_items ) ) {
+			$this->send_plugin_email( 'fail', $success_items, $failure_items );
+		} else {
+			$this->send_plugin_email( 'mixed', $success_items, $failure_items );
+		}
+	}
+
+	/**
 	 * Sends an email upon the completion or failure of a background core update.
 	 *
 	 * @since 3.7.0
@@ -752,7 +795,7 @@
 			// Support offer if available.
 			$body .= "\n\n" . sprintf(
 				/* translators: %s: Support email address. */
-				__( 'The WordPress team is willing to help you. Forward this email to %s and the team will work with you to make sure your site is working.' ),
+					__( 'The WordPress team is willing to help you. Forward this email to %s and the team will work with you to make sure your site is working.' ),
 				$core_update->support_email
 			);
 		} else {
@@ -844,6 +887,131 @@
 	}
 
 	/**
+	 * Sends an email upon the completion or failure of a plugin background update.
+	 *
+	 * @since 5.4.0
+	 *
+	 * @param string $type               The type of email to send. Can be one of 'success', 'failure', 'mixed'.
+	 * @param array  $successful_updates A list of plugin updates that succeeded.
+	 * @param array  $failed_updates     A list of plugin updates that failed.
+	 */
+	protected function send_plugin_email( $type, $successful_updates, $failed_updates ) {
+		/**
+		 * Filters whether to send an email following an automatic background plugin update.
+		 *
+		 * @since 5.4.0
+		 *
+		 * @param bool   $send    Whether to send the email. Default true.
+		 * @param string $type    The type of email to send. Can be one of
+		 *                        'success', 'failure' or 'mixed'.
+		 * @param object $updates The updates offered that were attempted.
+		 */
+		if ( ! apply_filters( 'auto_plugin_update_send_email', true, $type, $this->update_results['plugin'] ) ) {
+			return;
+		}
+
+		// No updates were attempted.
+		if ( empty( $successful_updates ) && empty( $failed_updates ) ) {
+			return;
+		}
+
+		$body = array();
+
+		switch ( $type ) {
+			case 'success':
+				/* translators: %s: Site title. */
+				$subject = __( '[%s] Plugins have automatically updated' );
+
+				break;
+
+			case 'fail':
+				/* translators: %s: Site title. */
+				$subject = __( '[%s] Plugins have failed to update' );
+				$body[]  = sprintf(
+					/* translators: %s: Home URL. */
+					__( 'Howdy! Failures occurred when attempting to update plugins on your site at %s.' ),
+					home_url()
+				);
+				$body[] = "\n";
+				$body[] = __( "Please check out your site now. It's possible that everything is working. If it says you need to update, you should do so." );
+
+				break;
+
+			case 'mixed':
+				/* translators: %s: Site title. */
+				$subject = __( '[%s] Some plugins have automatically updated' );
+
+				$body[] = sprintf(
+					/* translators: %s: Home URL. */
+					__( 'Howdy! There were some failures while attempting to update plugins on your site at %s.' ),
+					home_url()
+				);
+				$body[] = "\n";
+				$body[] = __( "Please check out your site now. It's possible that everything is working. If it says you need to update, you should do so." );
+				$body[] = "\n";
+
+				break;
+		}
+
+		if ( in_array( $type, array( 'fail', 'mixed' ), true ) && ! empty( $failed_updates ) ) {
+			$body[] = __( 'The following plugins failed to update:' );
+			// List failed updates.
+			foreach ( $failed_updates as $item ) {
+				/* translators: %s: Name of plugin. */
+				$body[] = ' ' . sprintf( __( '- %s' ), $item->name );
+			}
+			$body[] = "\n";
+		}
+
+		if ( in_array( $type, array( 'success', 'mixed' ), true ) && ! empty( $successful_updates ) ) {
+			// Successful updates.
+			$body[] = __( 'The following plugins were successfully updated:' );
+
+			foreach ( $successful_updates as $plugin ) {
+				/* translators: %s: Name of plugin. */
+				$body[] = ' ' . sprintf( __( '- %s' ), $plugin->name );
+			}
+		}
+
+		$body[] = "\n";
+
+		// Add a note about the support forums.
+		$body[] = __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
+		$body[] = __( 'https://wordpress.org/support/forums/' );
+
+		$body[] = "\n" . __( 'The WordPress Team' );
+
+		$body    = implode( "\n", $body );
+		$to      = get_site_option( 'admin_email' );
+		$subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) );
+		$headers = '';
+
+		$email = compact( 'to', 'subject', 'body', 'headers' );
+
+		/**
+		 * Filters the email sent following an automatic background plugin update.
+		 *
+		 * @since 5.4.0
+		 *
+		 * @param array $email {
+		 *     Array of email arguments that will be passed to wp_mail().
+		 *
+		 *     @type string $to      The email recipient. An array of emails
+		 *                           can be returned, as handled by wp_mail().
+		 *     @type string $subject The email's subject.
+		 *     @type string $body    The email message body.
+		 *     @type string $headers Any email headers, defaults to no headers.
+		 * }
+		 * @param string $type        The type of email being sent. Can be one of
+		 *                            'success', 'fail', 'mixed'.
+		 * @param object $updates    The updates that were attempted.
+		 */
+		$email = apply_filters( 'auto_plugin_update_email', $email, $type, $this->update_results['plugin'] );
+
+		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
+	}
+
+	/**
 	 * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
 	 *
 	 * @since 3.7.0
Index: src/wp-admin/includes/class-wp-upgrader.php
===================================================================
--- src/wp-admin/includes/class-wp-upgrader.php	(revision 47180)
+++ src/wp-admin/includes/class-wp-upgrader.php	(working copy)
@@ -783,6 +783,11 @@
 			return $working_dir;
 		}
 
+		// Enable maintenance mode for plugins.
+		if ( isset( $options['hook_extra'] ) && 'plugin' === $options['hook_extra']['type'] && is_plugin_active( $options['hook_extra']['plugin'] ) ) {
+			$this->maintenance_mode( true );
+		}
+
 		// With the given options, this installs it to the destination directory.
 		$result = $this->install_package(
 			array(
@@ -795,6 +800,11 @@
 			)
 		);
 
+		// Disable maintenance mode for plugins.
+		if ( isset( $options['hook_extra'] ) && 'plugin' === $options['hook_extra']['type'] && is_plugin_active( $options['hook_extra']['plugin'] ) ) {
+			$this->maintenance_mode( false );
+		}
+
 		$this->skin->set_result( $result );
 		if ( is_wp_error( $result ) ) {
 			$this->skin->error( $result );
