diff --git src/wp-admin/comment.php src/wp-admin/comment.php
index ad11f3e05b..18249c7bfa 100644
--- src/wp-admin/comment.php
+++ src/wp-admin/comment.php
@@ -165,6 +165,19 @@ switch ( $action ) {
 		?>
 <div id="message" class="notice notice-warning"><p><strong><?php _e( 'Caution:' ); ?></strong> <?php echo $caution_msg; ?></p></div>
 
+<?php
+if ( 0 === (int) $comment->comment_approved && true === (bool) get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) :
+	?>
+	<div id="message" class="notice notice-warning">
+		<p>
+			<?php
+			/* translators: %s is the comment author name. */
+			printf( esc_html__( '%s has opted in to receive a notification on comment’s approval.' ), get_comment_author( $comment ) );
+			?>
+		</p>
+	</div>
+<?php endif; ?>
+
 <table class="form-table comment-ays">
 <tr>
 <th scope="row"><?php _e( 'Author' ); ?></th>
diff --git src/wp-admin/css/dashboard.css src/wp-admin/css/dashboard.css
index 2411c79900..e76acdeaa5 100644
--- src/wp-admin/css/dashboard.css
+++ src/wp-admin/css/dashboard.css
@@ -821,6 +821,12 @@ body #dashboard-widgets .postbox form .submit {
 	position: relative;
 }
 
+#activity-widget #the-comment-list .comment-item .attention {
+	display: none;
+	color: #d54e21;
+	font-size: 90%;
+}
+
 #activity-widget #the-comment-list .avatar {
 	position: absolute;
 	top: 12px;
@@ -857,6 +863,10 @@ body #dashboard-widgets .postbox form .submit {
 	width: 4px;
 }
 
+#activity-widget #the-comment-list .unapproved .attention {
+	display: block;
+}
+
 #activity-widget #the-comment-list .spam-undo-inside .avatar,
 #activity-widget #the-comment-list .trash-undo-inside .avatar {
 	position: relative;
diff --git src/wp-admin/css/list-tables.css src/wp-admin/css/list-tables.css
index 1febe4e837..56334925a7 100644
--- src/wp-admin/css/list-tables.css
+++ src/wp-admin/css/list-tables.css
@@ -217,6 +217,12 @@
 	background-color: #f5f5f5;
 }
 
+#the-comment-list .comment .attention {
+	display: none;
+	color: #d54e21;
+	font-size: 90%;
+}
+
 #the-comment-list .unapproved th,
 #the-comment-list .unapproved td {
 	background-color: #fef7f1;
@@ -230,6 +236,10 @@
 	margin-left: 4px;
 }
 
+#the-comment-list .comment.unapproved .attention {
+	display: block;
+}
+
 #the-comment-list .approve a {
 	color: #006505;
 }
diff --git src/wp-admin/edit-form-comment.php src/wp-admin/edit-form-comment.php
index ef0818c510..81614200db 100644
--- src/wp-admin/edit-form-comment.php
+++ src/wp-admin/edit-form-comment.php
@@ -16,6 +16,19 @@ if ( ! defined( 'ABSPATH' ) ) {
 <div class="wrap">
 <h1><?php _e( 'Edit Comment' ); ?></h1>
 
+<?php
+if ( 0 === (int) $comment->comment_approved && true === (bool) get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) :
+	?>
+	<div id="message" class="notice notice-warning">
+		<p>
+			<?php
+			/* translators: %s is the comment author name. */
+			printf( esc_html__( '%s has opted in to receive a notification on comment’s approval.' ), get_comment_author( $comment ) );
+			?>
+		</p>
+	</div>
+<?php endif; ?>
+
 <div id="poststuff">
 <input type="hidden" name="action" value="editedcomment" />
 <input type="hidden" name="comment_ID" value="<?php echo esc_attr( $comment->comment_ID ); ?>" />
diff --git src/wp-admin/includes/class-wp-comments-list-table.php src/wp-admin/includes/class-wp-comments-list-table.php
index 563187ffe4..daf837aaa6 100644
--- src/wp-admin/includes/class-wp-comments-list-table.php
+++ src/wp-admin/includes/class-wp-comments-list-table.php
@@ -882,6 +882,11 @@ class WP_Comments_List_Table extends WP_List_Table {
 			}
 		}
 
+		if ( 0 === (int) $comment->comment_approved && true === (bool) get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) {
+			/* translators: %s is the comment author name. */
+			echo '<p class="attention"><span class="dashicons dashicons-info"></span> ' . sprintf( esc_html__( '%s has opted in to receive a notification on comment’s approval.' ), get_comment_author( $comment ) ) . '</p>';
+		}
+
 		comment_text( $comment );
 
 		if ( $this->user_can ) {
diff --git src/wp-admin/includes/dashboard.php src/wp-admin/includes/dashboard.php
index 2f438067da..79e7dcfa73 100644
--- src/wp-admin/includes/dashboard.php
+++ src/wp-admin/includes/dashboard.php
@@ -827,8 +827,19 @@ function _wp_dashboard_recent_comments_row( &$comment, $show_date = true ) {
 				}
 				?>
 			</p>
-
 				<?php
+				if ( 0 === (int) $comment->comment_approved && true === (bool) get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) :
+					?>
+						<p class="attention">
+							<span class="dashicons dashicons-info"></span>
+							<?php
+							/* translators: %s is the comment author name. */
+							printf( esc_html__( '%s has opted in to receive a notification on comment’s approval.' ), get_comment_author( $comment ) );
+							?>
+						</p>
+					<?php
+					endif;
+
 			else :
 				switch ( $comment->comment_type ) {
 					case 'pingback':
diff --git src/wp-comments-post.php src/wp-comments-post.php
index 06cbd460f4..3b1a8b44ca 100644
--- src/wp-comments-post.php
+++ src/wp-comments-post.php
@@ -22,37 +22,53 @@ require __DIR__ . '/wp-load.php';
 
 nocache_headers();
 
-$comment = wp_handle_comment_submission( wp_unslash( $_POST ) );
-if ( is_wp_error( $comment ) ) {
-	$data = (int) $comment->get_error_data();
-	if ( ! empty( $data ) ) {
+if ( isset( $_POST['wp-comment-approved-notification-optin'], $_POST['comment_ID'], $_POST['moderation-hash'] ) ) {
+	$comment = get_comment( $_POST['comment_ID'] );
+
+	if ( ! is_null( $comment->comment_ID ) && hash_equals( $_POST['moderation-hash'], wp_hash( $comment->comment_date_gmt ) ) ) {
+		update_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true );
+	} else {
 		wp_die(
-			'<p>' . $comment->get_error_message() . '</p>',
-			__( 'Comment Submission Failure' ),
+			'<p>' . __( 'Sorry we haven’t found your comment.' ) . '</p>',
+			__( 'Comment Notification opt-in failure' ),
 			array(
-				'response'  => $data,
 				'back_link' => true,
 			)
 		);
-	} else {
-		exit;
 	}
-}
+} else {
+	$comment = wp_handle_comment_submission( wp_unslash( $_POST ) );
+	if ( is_wp_error( $comment ) ) {
+		$data = (int) $comment->get_error_data();
+		if ( ! empty( $data ) ) {
+			wp_die(
+				'<p>' . $comment->get_error_message() . '</p>',
+				__( 'Comment Submission Failure' ),
+				array(
+					'response'  => $data,
+					'back_link' => true,
+				)
+			);
+		} else {
+			exit;
+		}
+	}
 
-$user            = wp_get_current_user();
-$cookies_consent = ( isset( $_POST['wp-comment-cookies-consent'] ) );
+	$user            = wp_get_current_user();
+	$cookies_consent = ( isset( $_POST['wp-comment-cookies-consent'] ) );
 
-/**
- * Perform other actions when comment cookies are set.
- *
- * @since 3.4.0
- * @since 4.9.6 The `$cookies_consent` parameter was added.
- *
- * @param WP_Comment $comment         Comment object.
- * @param WP_User    $user            Comment author's user object. The user may not exist.
- * @param bool       $cookies_consent Comment author's consent to store cookies.
- */
-do_action( 'set_comment_cookies', $comment, $user, $cookies_consent );
+	/**
+	 * Perform other actions when comment cookies are set.
+	 *
+	 * @since 3.4.0
+	 * @since 4.9.6 The `$cookies_consent` parameter was added.
+	 *
+	 * @param WP_Comment $comment         Comment object.
+	 * @param WP_User    $user            Comment author's user object. The user may not exist.
+	 * @param bool       $cookies_consent Comment author's consent to store cookies.
+	 */
+	do_action( 'set_comment_cookies', $comment, $user, $cookies_consent );
+}
 
 $location = empty( $_POST['redirect_to'] ) ? get_comment_link( $comment ) : $_POST['redirect_to'] . '#comment-' . $comment->comment_ID;
 
diff --git src/wp-includes/class-walker-comment.php src/wp-includes/class-walker-comment.php
index 9df2d20b3b..9c5add1c49 100644
--- src/wp-includes/class-walker-comment.php
+++ src/wp-includes/class-walker-comment.php
@@ -40,6 +40,16 @@ class Walker_Comment extends Walker {
 		'id'     => 'comment_ID',
 	);
 
+	/**
+	 * Backward compability checks.
+	 *
+	 * @since 5.7.0
+	 * @var array
+	 */
+	public $backcompat = array(
+		'needs_comment_approval_notification_output' => true,
+	);
+
 	/**
 	 * Starts the list before the elements are added.
 	 *
@@ -259,6 +269,7 @@ class Walker_Comment extends Walker {
 	 * to the comment cookies.
 	 *
 	 * @since 5.4.2
+	 * @since 5.7.0 Adds backcompatibilty output if needed.
 	 *
 	 * @param string          $comment_text Text of the current comment.
 	 * @param WP_Comment|null $comment      The comment object. Null if not found.
@@ -272,9 +283,87 @@ class Walker_Comment extends Walker {
 			$comment_text = wp_kses( $comment_text, array() );
 		}
 
+		/*
+		 * Checks if we need to output the comment approval notification/form.
+		 */
+		if ( $this->backcompat['needs_comment_approval_notification_output'] ) {
+			$comment_text = $this->comment_approval_notification_form( $comment, true ) . "\n" . $comment_text;
+		}
+
 		return $comment_text;
 	}
 
+	/**
+	 * Outputs the awaiting moderation text.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param WP_Comment $comment Comment to display.
+	 */
+	protected function awaiting_moderation_text( $comment ) {
+
+		if ( "0" === $comment->comment_approved ) {
+			$commenter = wp_get_current_commenter();
+			if ( $commenter['comment_author_email'] ) {
+				$moderation_note = __( 'Your comment is awaiting moderation.' );
+			} else {
+				$moderation_note = __( 'Your comment is awaiting moderation. This is a preview, your comment will be visible after it has been approved.' );
+			}
+
+			printf( '<em class="comment-awaiting-moderation">%s</em>', esc_html( $moderation_note ) );
+		}
+	}
+
+	/**
+	 * Gets the comment's approval notification/form.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param WP_Comment $comment Comment to display.
+	 * @param bool $skip_backcompat True to skip the backward compability output.
+	 *                              False otherwise.
+	 * @return string HTML Output.
+	 */
+	protected function comment_approval_notification_form( $comment, $skip_backcompat = false ) {
+		$comment_approval_output = '';
+
+		if ( "0" === $comment->comment_approved && has_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' ) ) {
+			if ( true === (bool) get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) {
+				$comment_approval_output = sprintf(
+					'<p><em class="wp-comment-approved-notification-optedin">%s</em></p>',
+					esc_html__( 'You will receive an email when your comment has been approved.' )
+				);
+			} else {
+				$comment_approval_output = sprintf(
+					'<form action="%1$s" method="post">
+						<p>
+							<label for="wp-comment-approved-notification-optin">
+								<input type="checkbox" id="wp-comment-approved-notification-optin" name="wp-comment-approved-notification-optin">
+								%2$s
+							</label>
+						</p>
+						<input type="hidden" name="comment_ID" value="%3$s">
+						<input type="hidden" name="moderation-hash" value="%4$s">
+						<input type="submit" class="button" value="%5$s">
+					</form>',
+					esc_url( site_url( '/wp-comments-post.php' ) ),
+					esc_html__( 'I want to be notified by email when my comment is approved.' ),
+					absint( $comment->comment_ID ),
+					wp_hash( $comment->comment_date_gmt ),
+					esc_html_x( 'Save', 'comment approved notification form' )
+				);
+			}
+		}
+
+		// Skip the backcompat output.
+		if ( $skip_backcompat ) {
+			$this->backcompat['needs_comment_approval_notification_output'] = false;
+		}
+
+		// Return the approval notification/form.
+		return $comment_approval_output;
+	}
+
 	/**
 	 * Outputs a single comment.
 	 *
@@ -297,12 +386,6 @@ class Walker_Comment extends Walker {
 
 		$commenter          = wp_get_current_commenter();
 		$show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author'];
-
-		if ( $commenter['comment_author_email'] ) {
-			$moderation_note = __( 'Your comment is awaiting moderation.' );
-		} else {
-			$moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' );
-		}
 		?>
 		<<?php echo $tag; ?> <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?> id="comment-<?php comment_ID(); ?>">
 		<?php if ( 'div' !== $args['style'] ) : ?>
@@ -328,10 +411,14 @@ class Walker_Comment extends Walker {
 			);
 			?>
 		</div>
-		<?php if ( '0' == $comment->comment_approved ) : ?>
-		<em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em>
-		<br />
-		<?php endif; ?>
+
+		<?php
+		// Output the comment moderation feedback if needed.
+		$this->awaiting_moderation_text( $comment );
+
+		// Output the comment approval notification form if needed.
+		echo $this->comment_approval_notification_form( $comment, true );
+		?>
 
 		<div class="comment-meta commentmetadata">
 			<?php
@@ -401,12 +488,6 @@ class Walker_Comment extends Walker {
 
 		$commenter          = wp_get_current_commenter();
 		$show_pending_links = ! empty( $commenter['comment_author'] );
-
-		if ( $commenter['comment_author_email'] ) {
-			$moderation_note = __( 'Your comment is awaiting moderation.' );
-		} else {
-			$moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' );
-		}
 		?>
 		<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?>>
 			<article id="div-comment-<?php comment_ID(); ?>" class="comment-body">
@@ -450,9 +531,13 @@ class Walker_Comment extends Walker {
 						?>
 					</div><!-- .comment-metadata -->
 
-					<?php if ( '0' == $comment->comment_approved ) : ?>
-					<em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em>
-					<?php endif; ?>
+					<?php
+					// Output the comment moderation feedback if needed.
+					$this->awaiting_moderation_text( $comment );
+
+					// Output the comment approval notification form if needed.
+					echo $this->comment_approval_notification_form( $comment, true );
+					?>
 				</footer><!-- .comment-meta -->
 
 				<div class="comment-content">
diff --git src/wp-includes/comment.php src/wp-includes/comment.php
index 116e66a57c..3d5cbee07c 100644
--- src/wp-includes/comment.php
+++ src/wp-includes/comment.php
@@ -2348,6 +2348,96 @@ function wp_new_comment_notify_postauthor( $comment_ID ) {
 	return wp_notify_postauthor( $comment_ID );
 }
 
+/**
+ * Notify a comment author when their comment gets approved.
+ *
+ * This notification is only sent once when the comment status
+ * changes from unapproved to approved.
+ *
+ * @since 5.7.0
+ *
+ * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
+ * @return bool Whether the email was sent successfully.
+ */
+function wp_new_comment_notify_comment_author( $comment_id ) {
+	$comment = get_comment( $comment_id );
+
+	if ( ! $comment ) {
+		return false;
+	}
+
+	$post           = get_post( $comment->comment_post_ID );
+	$comment_author = get_user_by( 'email', $comment->comment_author_email );
+
+	if ( ! $post ) {
+		return false;
+	}
+
+	// Make sure the comment author can be notified by email.
+	if ( empty( $comment->comment_author_email ) ) {
+		return false;
+	}
+
+	if ( true !== (bool) get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) {
+		return false;
+	}
+
+	/**
+	 * The blogname option is escaped with esc_html when
+	 * saved into the database, we need to reverse this for
+	 * the plain text area of the email.
+	 */
+	$blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
+
+	/* translators: 1: blog name, 2: post title */
+	$subject = sprintf( __( '[%1$s] Your comment on "%2$s" has been approved' ), $blogname, $post->post_title );
+
+	if ( ! empty( $comment->comment_author ) ) {
+		/* translators: 1: comment author's name */
+		$notify_message = sprintf( __( 'Howdy %s,' ), $comment->comment_author ) . "\r\n\r\n";
+	} else {
+		$notify_message = __( 'Howdy,' ) . "\r\n\r\n";
+	}
+
+	/* translators: 1: post title */
+	$notify_message .= sprintf( __( 'Your comment on the post "%s" has been approved.' ), $post->post_title ) . "\r\n\r\n";
+	/* translators: 1: comment permalink */
+	$notify_message .= sprintf( __( 'View comment: %s' ), get_comment_link( $comment ) ) . "\r\n";
+
+	$comment_approval_notification = array(
+		'subject' => $subject,
+		'message' => $notify_message,
+	);
+
+	/**
+	 * Filters the content of the email sent to notify a comment author their comment has been approved.
+	 *
+	 * Content should be formatted for transmission via wp_mail().
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param array $comment_approval_notification {
+	 *     Used to build wp_mail().
+	 *
+	 *     @type string $subject The subject of the email.
+	 *     @type string $message The content of the email.
+	 * }
+	 * @param WP_Comment $comment Comment object.
+	 */
+	$comment_approval_notification = apply_filters( 'comment_approval_notification', $comment_approval_notification, $comment );
+
+	$sent = wp_mail(
+		$comment->comment_author_email,
+		wp_specialchars_decode( $comment_approval_notification['subject'] ),
+		$comment_approval_notification['message']
+	);
+
+	// Delete the opt-in now the notification has been sent.
+	delete_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin' );
+
+	return $sent;
+}
+
 /**
  * Sets the status of a comment.
  *
diff --git src/wp-includes/default-filters.php src/wp-includes/default-filters.php
index 334a12a929..6a92a2be07 100644
--- src/wp-includes/default-filters.php
+++ src/wp-includes/default-filters.php
@@ -463,6 +463,7 @@ add_action( 'comment_post', 'wp_new_comment_notify_postauthor' );
 add_action( 'after_password_reset', 'wp_password_change_notification' );
 add_action( 'register_new_user', 'wp_send_new_user_notifications' );
 add_action( 'edit_user_created_user', 'wp_send_new_user_notifications', 10, 2 );
+add_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' );
 
 // REST API actions.
 add_action( 'init', 'rest_api_init' );
diff --git tests/phpunit/tests/comment.php tests/phpunit/tests/comment.php
index c3013ad2c6..c244823989 100644
--- tests/phpunit/tests/comment.php
+++ tests/phpunit/tests/comment.php
@@ -554,6 +554,58 @@ class Tests_Comment extends WP_UnitTestCase {
 		return $notify_message;
 	}
 
+	/**
+	 * @ticket 33717
+	 */
+	public function test_wp_new_comment_notify_comment_author_once_only() {
+		$c = self::factory()->comment->create(
+			array(
+				'comment_post_ID'      => self::$post_id,
+				'comment_approved'     => '0',
+				'comment_author_email' => 'foo@bar.mail',
+			)
+		);
+
+		// The user subscribed to receive an email once comment is approved.
+		update_comment_meta( $c, '_wp_comment_author_notification_optin', true );
+
+		// For the purpose of the test we are removing this hook to directly use the function to notify the comment author.
+		remove_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' );
+
+		// Approve the comment.
+		wp_set_comment_status( $c, 'approve' );
+
+		$sent = wp_new_comment_notify_comment_author( $c );
+		$this->assertTrue( $sent );
+
+		$resent = wp_new_comment_notify_comment_author( $c );
+		$this->assertFalse( $resent );
+	}
+
+	/**
+	 * @ticket 33717
+	 */
+	public function test_wp_new_comment_notify_comment_author_has_not_optin() {
+		$c = self::factory()->comment->create(
+			array(
+				'comment_post_ID'      => self::$post_id,
+				'comment_approved'     => '0',
+				'comment_author_email' => 'bat@man.mail',
+			)
+		);
+
+		// For the purpose of the test we are removing this hook to directly use the function to notify the comment author.
+		remove_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' );
+
+		// Approve the comment.
+		wp_set_comment_status( $c, 'approve' );
+
+		$sent = wp_new_comment_notify_comment_author( $c );
+
+		// The user hasn't subscribed to receive an email, no email should be sent.
+		$this->assertFalse( $sent );
+	}
+
 	/**
 	 * @ticket 12431
 	 */
