This code is helpful when you're testing functionality locally that involves sending transactional emails and you don't want to deal with any external services or worry about managing different sets of SMTP credentials across environments.

It prevents emails from leaving your environment and instead saves them in wp-content/mail-logs.

I also installed the HTML Preview extension in VS Code so I can easily open the email directly in my editor.

mail-logger.php

<?php
/**
 * Plugin Name: Mail Logger
 * Description: Log emails locally for testing
 * Version: 1.0
 */

/**
 * Log wp_mail actions to HTML files for local testing.
 *
 * Creates detailed email logs in /wp-content/mail-logs with automatic
 * cleanup of files older than 2 days or beyond the 10 most recent.
 */
add_action('wp_mail', function ($args) {
	$log_dir = WP_CONTENT_DIR . '/mail-logs';

	// Ensure log directory exists.
	if (!is_dir($log_dir)) {
		mkdir($log_dir, 0755, true);
	}

	// Extract email details.
	$to      = is_array($args['to']) ? implode(', ', $args['to']) : $args['to'];
	$subject = $args['subject'];
	$message = $args['message'];

	// Generate HTML email log.
	$html = sprintf(
		'<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Email: %s</title>
	<style>
		body {
			font-family: Arial, sans-serif;
			background: #f5f5f5;
			padding: 20px;
		}
		.header {
			background: #f0f0f0;
			padding: 20px;
			margin-bottom: 20px;
			border-radius: 5px;
		}
		.header h1 {
			margin: 0 0 10px 0;
			font-size: 18px;
		}
		.meta {
			color: #666;
			font-size: 14px;
		}
		.content {
			background: #fff;
			padding: 20px;
			border-radius: 5px;
		}
	</style>
</head>
<body>
	<div class="header">
		<h1>%s</h1>
		<div class="meta">
			<p><strong>To:</strong> %s</p>
			<p><strong>Date:</strong> %s</p>
		</div>
	</div>
	<div class="content">
		%s
	</div>
</body>
</html>',
		htmlspecialchars($subject),
		htmlspecialchars($subject),
		htmlspecialchars($to),
		date('Y-m-d H:i:s'),
		$message
	);

	// Save email log with sortable timestamp prefix.
	$filename = $log_dir . '/' . sprintf('%010d', PHP_INT_MAX - time()) . '_' . date('Y-m-d_H-i-s') . '_' . sanitize_title($subject) . '.html';
	file_put_contents($filename, $html);

	// Clean up old log files.
	$files         = array_diff(scandir($log_dir, SCANDIR_SORT_DESCENDING), array('..', '.'));
	$two_days_ago  = time() - (2 * 24 * 60 * 60);

	foreach ($files as $index => $file) {
		$file_path = $log_dir . '/' . $file;

		if (is_file($file_path)) {
			// Delete if older than 2 days OR beyond the 10 most recent files.
			if (filemtime($file_path) < $two_days_ago || $index >= 10) {
				@unlink($file_path);
			}
		}
	}
}, 10);

/**
 * Prevent emails from actually being sent.
 *
 * Intercepts wp_mail before sending to ensure only local logging occurs.
 */
add_filter('pre_wp_mail', function () {
	return false;
});