diff --git a/src/wp-includes/class-wp-fatal-error-handler.php b/src/wp-includes/class-wp-fatal-error-handler.php
index 7e4f6d8c3c..f3ee4985d4 100644
--- a/src/wp-includes/class-wp-fatal-error-handler.php
+++ b/src/wp-includes/class-wp-fatal-error-handler.php
@@ -183,6 +183,12 @@ class WP_Fatal_Error_Handler {
 			$message = __( 'There has been a critical error on your website.' );
 		}
 
+		$message .= sprintf(
+			'<a href="%s">%s</a>',
+			esc_url( add_query_arg( 'action', 'request_rm', wp_login_url() ) ),
+			__( 'Request an email with a Recovery Mode link.' )
+		);
+
 		$message = sprintf(
 			'<p>%s</p><p><a href="%s">%s</a></p>',
 			$message,
diff --git a/src/wp-includes/class-wp-recovery-mode-email-service.php b/src/wp-includes/class-wp-recovery-mode-email-service.php
index 5b0dd2e4db..db4efc1bf3 100644
--- a/src/wp-includes/class-wp-recovery-mode-email-service.php
+++ b/src/wp-includes/class-wp-recovery-mode-email-service.php
@@ -161,7 +161,9 @@ If your site appears broken and you can\'t access your dashboard normally, WordP
 
 ###LINK###
 
-To keep your site safe, this link will expire in ###EXPIRES###. Don\'t worry about that, though: a new link will be emailed to you if the error occurs again after it expires.
+To keep your site safe, this link will expire in ###EXPIRES###. Don\'t worry about that, though: you can request a new link at the page below:
+
+###REQUEST_LINK###
 
 When seeking help with this issue, you may be asked for some of the following information:
 ###DEBUG###
@@ -178,6 +180,7 @@ When seeking help with this issue, you may be asked for some of the following in
 				'###PAGEURL###',
 				'###SUPPORT###',
 				'###DEBUG###',
+				'###REQUEST_LINK###',
 			),
 			array(
 				$url,
@@ -188,6 +191,7 @@ When seeking help with this issue, you may be asked for some of the following in
 				home_url( $_SERVER['REQUEST_URI'] ),
 				$support,
 				implode( "\r\n", $debug ),
+				add_query_arg( 'action', 'request_rm', wp_login_url() ),
 			),
 			$message
 		);
@@ -224,6 +228,95 @@ When seeking help with this issue, you may be asked for some of the following in
 		return $sent;
 	}
 
+	/**
+	 * Sends the Recovery Mode email when a user requests it.
+	 *
+	 * @since 5.4.0
+	 *
+	 * @param string $to         The email address of the recipient.
+	 * @param int    $rate_limit Number of seconds before another email can be sent.
+	 *
+	 * @return bool Whether the email was sent successfully.
+	 */
+	public function send_requested_recovery_mode_email( $to, $rate_limit ) {
+		$url      = $this->link_service->generate_url();
+		$blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
+
+		$switched_locale = false;
+
+		// The switch_to_locale() function is loaded before it can actually be used.
+		if ( function_exists( 'switch_to_locale' ) && isset( $GLOBALS['wp_locale_switcher'] ) ) {
+			$switched_locale = switch_to_locale( get_locale() );
+		}
+
+		// This filter is already documented in wp-includes/class-wp-recovery-mode-email-service.php
+		$support = apply_filters( 'recovery_email_support_info', __( 'Please contact your host for assistance with investigating this issue further.' ) );
+
+		/* translators: Do not translate LINK, EXPIRES, CAUSE, DETAILS, SITEURL, PAGEURL, SUPPORT. DEBUG: those are placeholders. */
+		$message = __(
+			'Howdy!
+
+You requested a link to enter Recovery Mode.
+
+First, visit your website (###SITEURL###) and check for any visible issues. Next, visit the page where the error was caught and check for any visible issues.
+
+###SUPPORT###
+
+If your site appears broken and you can\'t access your dashboard normally, WordPress now has a special "recovery mode". This lets you safely login to your dashboard and investigate further.
+
+###LINK###
+
+To keep your site safe, this link will expire in ###EXPIRES###. Don\'t worry about that, though: you can request a new link at any time.'
+		);
+		$message = str_replace(
+			array(
+				'###LINK###',
+				'###EXPIRES###',
+				'###SITEURL###',
+				'###SUPPORT###',
+			),
+			array(
+				$url,
+				human_time_diff( time() + $rate_limit ),
+				home_url( '/' ),
+				$support,
+			),
+			$message
+		);
+
+		$email = array(
+			'to'      => $to,
+			/* translators: %s: Site title. */
+			'subject' => __( '[%s] Your Recovery Mode Link' ),
+			'message' => $message,
+			'headers' => '',
+		);
+
+		/**
+		 * Filter the contents of the requested Recovery Mode email.
+		 *
+		 * @since 5.4.0
+		 *
+		 * @param array  $email Used to build wp_mail().
+		 * @param string $url   URL to enter recovery mode.
+		 */
+		$email = apply_filters( 'requested_recovery_mode_email', $email, $url );
+
+		$sent = wp_mail(
+			$email['to'],
+			wp_specialchars_decode( sprintf( $email['subject'], $blogname ) ),
+			$email['message'],
+			$email['headers']
+		);
+
+		if ( $switched_locale ) {
+			restore_previous_locale();
+		}
+
+		return $sent;
+	}
+
+
 	/**
 	 * Gets the email address to send the recovery mode link to.
 	 *
diff --git a/src/wp-includes/class-wp-recovery-mode.php b/src/wp-includes/class-wp-recovery-mode.php
index 608864d6ee..f3d007a481 100644
--- a/src/wp-includes/class-wp-recovery-mode.php
+++ b/src/wp-includes/class-wp-recovery-mode.php
@@ -95,6 +95,12 @@ class WP_Recovery_Mode {
 		add_action( 'login_form_' . self::EXIT_ACTION, array( $this, 'handle_exit_recovery_mode' ) );
 		add_action( 'recovery_mode_clean_expired_keys', array( $this, 'clean_expired_keys' ) );
 
+		add_action( 'wp_login', array( $this, 'store_recovery_mode_email' ), 10, 2 );
+		add_action( 'profile_update', array( $this, 'update_email_list' ) );
+		add_action( 'add_user_role', array( $this, 'update_email_list' ) );
+		add_action( 'set_user_role', array( $this, 'update_email_list' ) );
+		add_action( 'remove_user_role', array( $this, 'update_email_list' ) );
+
 		if ( ! wp_next_scheduled( 'recovery_mode_clean_expired_keys' ) && ! wp_installing() ) {
 			wp_schedule_event( time(), 'daily', 'recovery_mode_clean_expired_keys' );
 		}
@@ -112,6 +118,7 @@ class WP_Recovery_Mode {
 			return;
 		}
 
+		$this->request_recovery_mode();
 		$this->link_service->handle_begin_link( $this->get_link_ttl() );
 	}
 
@@ -195,6 +202,17 @@ class WP_Recovery_Mode {
 		$this->redirect_protected();
 	}
 
+	/**
+	 * When a user logs in, check if they have recovery mode permissions, and add them
+	 * to the list of valid recovery mode emails.
+	 *
+	 * @param string $username Username.
+	 * @param WP_User $user    WP_User object of the logged-in user.
+	 */
+	public function store_recovery_mode_email( $username, $user ) {
+		$this->update_email_list( $user->ID );
+	}
+
 	/**
 	 * Ends the current recovery mode session.
 	 *
@@ -466,4 +484,106 @@ class WP_Recovery_Mode {
 		wp_safe_redirect( $url );
 		exit;
 	}
+
+	/**
+	 * Updates the list of Recovery Mode email addresses.
+	 *
+	 * @since 5.4.0
+	 *
+	 * @param int $user_id The user to add or remove from the list.
+	 */
+	private function update_email_list( $user_id ) {
+		$user = get_userdata( $user_id );
+
+		if ( ! $user ) {
+			return;
+		}
+
+		$has_cap  = $user->has_cap( 'resume_plugins' ) || $user->has_cap( 'resume_themes' );
+		$emails   = get_option( 'recovery_mode_emails', array() );
+		$included = in_array( $user->user_email, $emails, true );
+
+		if ( $has_cap && $included ) {
+			return;
+		}
+
+		if ( ! $has_cap && ! $included ) {
+			return;
+		}
+
+		if ( $has_cap ) {
+			$emails[] = $user->user_email;
+		} else {
+			unset( $emails[ array_search( $user->user_email, $emails, true ) ] );
+		}
+
+		update_option( 'recovery_mode_emails', $emails );
+	}
+
+	/**
+	 * Handles when a user requests a recovery mode link for their email address.
+	 *
+	 * @since 5.2.0
+	 */
+	private function request_recovery_mode() {
+		if ( ! isset( $GLOBALS['pagenow'] ) || 'wp-login.php' !== $GLOBALS['pagenow'] ) {
+			return;
+		}
+
+		if ( ! isset( $_GET['action'] ) || 'request_rm' !== $_GET['action'] ) {
+			return;
+		}
+
+		if ( ! empty( $_POST['rm_email'] ) ) {
+			$allowed   = get_option( 'recovery_mode_emails', array() );
+			$allowed[] = get_option( 'admin_email' );
+
+			if ( defined( 'RECOVERY_MODE_EMAIL' ) && RECOVERY_MODE_EMAIL ) {
+				$allowed[] = RECOVERY_MODE_EMAIL;
+			}
+
+			$email = $_POST['rm_email'];
+
+			if ( ! in_array( $email, get_option( 'recovery_mode_emails', array() ), true ) ) {
+				wp_die(
+					__( 'Email does not belong to a user with recovery mode capabilities.' ),
+					'',
+					array(
+						'back_link' => true,
+						'response'  => 403,
+					)
+				);
+			}
+
+			if ( ! function_exists( 'wp_generate_password' ) ) {
+				require_once ABSPATH . WPINC . '/pluggable.php';
+			}
+
+			$sent = $this->email_service->send_requested_recovery_mode_email( $email, $this->get_email_rate_limit() );
+
+			if ( ! $sent ) {
+				wp_die(
+					sprintf(
+					/* translators: %s: mail() */
+						__( 'The email could not be sent. Possible reason: your host may have disabled the %s function.' ),
+						'mail()'
+					),
+					500
+				);
+			}
+
+			wp_die( __( 'Recovery email sent.' ), __( 'Request a Recovery Mode Link' ), 200 );
+		}
+
+		ob_start();
+		?>
+		<form method="POST" action="<?php esc_url( add_query_arg( 'action', 'request_rm', wp_login_url() ) ); ?>">
+			<label for="email"><?php _e( 'Recovery Mode Email' ); ?></label>
+			<input type="email" id="email" name="rm_email">
+
+			<button class="button"><?php _e( 'Request Email' ); ?></button>
+		</form>
+		<?php
+		wp_die( ob_get_clean(), __( 'Request a Recovery Mode Link' ), 200 );
+	}
 }
