Index: src/wp-comments-post.php
===================================================================
--- src/wp-comments-post.php	(revision 50031)
+++ src/wp-comments-post.php	(working copy)
@@ -22,6 +22,22 @@
 
 nocache_headers();
 
+if ( isset( $_POST['wp-comment-approved-notification-optin'], $_POST['comment_ID'], $_POST['moderation-hash'] ) ) {
+	$comment = get_comment( $_POST['comment_ID'] );
+
+	if ( $comment && 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>' . __( 'Invalid comment ID.' ) . '</p>',
+			__( 'Comment Notification Opt-in Failure' ),
+			array(
+				'response'  => 404,
+				'back_link' => true,
+			)
+		);
+	}
+} else {
 $comment = wp_handle_comment_submission( wp_unslash( $_POST ) );
 if ( is_wp_error( $comment ) ) {
 	$data = (int) $comment->get_error_data();
@@ -53,6 +69,7 @@
  * @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;
 
Index: src/wp-includes/class-walker-comment.php
===================================================================
--- src/wp-includes/class-walker-comment.php	(revision 50031)
+++ src/wp-includes/class-walker-comment.php	(working copy)
@@ -41,6 +41,17 @@
 	);
 
 	/**
+	 * Backward compability checks.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @var bool[]
+	 */
+	public $backcompat = array(
+		'needs_comment_approval_notification_output' => true,
+	);
+
+	/**
 	 * Starts the list before the elements are added.
 	 *
 	 * @since 2.7.0
@@ -259,6 +270,7 @@
 	 * 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,10 +284,93 @@
 			$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 ) {
+			return;
+		}
+
+		$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 or form for opting in.
+	 *
+	 * @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 ( 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 is 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 approval 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.
 	 *
 	 * @since 3.6.0
@@ -297,12 +392,6 @@
 
 		$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 +417,14 @@
 			);
 			?>
 		</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 +494,6 @@
 
 		$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 +537,13 @@
 						?>
 					</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">
Index: src/wp-includes/comment.php
===================================================================
--- src/wp-includes/comment.php	(revision 50031)
+++ src/wp-includes/comment.php	(working copy)
@@ -2349,6 +2349,114 @@
 }
 
 /**
+ * Notifies the 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.
+ */
+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 );
+
+	if ( ! $post ) {
+		return false;
+	}
+
+	// Make sure the comment author can be notified by email.
+	if ( empty( $comment->comment_author_email ) ) {
+		return false;
+	}
+
+	if ( ! 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 );
+
+	$subject = sprintf(
+		/* translators: 1: blog name, 2: post title */
+		__( '[%1$s] Your comment on "%2$s" has been approved' ),
+		$blogname,
+		$post->post_title
+	);
+
+	if ( ! empty( $comment->comment_author ) ) {
+		$notify_message = sprintf(
+			/* translators: 1: comment author's name */
+			__( 'Howdy %s,' ),
+			$comment->comment_author
+		) . "\r\n\r\n";
+	} else {
+		$notify_message = __( 'Howdy,' ) . "\r\n\r\n";
+	}
+
+	$notify_message .= sprintf(
+		/* translators: 1: post title */
+		__( 'Your comment on "%s" has been approved.' ),
+		$post->post_title
+	) . "\r\n\r\n";
+
+	$notify_message .= sprintf(
+		/* translators: 1: comment permalink */
+		__( 'View comment: %s' ),
+		get_comment_link( $comment )
+	) . "\r\n";
+
+	$email = array(
+		'to'      => $comment->comment_author_email,
+		'subject' => $subject,
+		'message' => $notify_message,
+		'headers' => '',
+	);
+
+	/**
+	 * Filters the contents of the email sent to notify a comment author that their comment was approved.
+	 *
+	 * Content should be formatted for transmission via wp_mail().
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param array      $email   {
+	 *     Used to build wp_mail().
+	 *
+	 *     @type string $to      The email address of the comment author.
+	 *     @type string $subject The subject of the email.
+	 *     @type string $message The content of the email.
+	 *     @type string $headers Headers.
+	 * }
+	 * @param WP_Comment $comment Comment object.
+	 */
+	$email = apply_filters( 'comment_approval_notification', $email, $comment );
+
+	$sent = wp_mail(
+		$email['to'],
+		wp_specialchars_decode( $email['subject'] ),
+		$email['message'],
+		$email['headers']
+	);
+
+	// 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.
  *
  * The {@see 'wp_set_comment_status'} action is called after the comment is handled.
Index: src/wp-includes/default-filters.php
===================================================================
--- src/wp-includes/default-filters.php	(revision 50031)
+++ src/wp-includes/default-filters.php	(working copy)
@@ -467,6 +467,7 @@
 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' );
Index: tests/phpunit/tests/comment.php
===================================================================
--- tests/phpunit/tests/comment.php	(revision 50031)
+++ tests/phpunit/tests/comment.php	(working copy)
@@ -555,6 +555,58 @@
 	}
 
 	/**
+	 * @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 comment author 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 );
+		$resent = wp_new_comment_notify_comment_author( $c );
+
+		$this->assertTrue( $sent );
+		$this->assertFalse( $resent );
+	}
+
+	/**
+	 * @ticket 33717
+	 */
+	public function test_wp_new_comment_notify_comment_author_has_not_opted_in() {
+		$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 comment author hasn't subscribed to receive an email, no email should be sent.
+		$this->assertFalse( $sent );
+	}
+
+	/**
 	 * @ticket 12431
 	 */
 	public function test_wp_new_comment_with_meta() {
