How to Monitor WordPress Email Delivery with MU-Plugin
If you’ve ever wondered whether your WordPress site is actually sending emails or why they’re failing this tutorial will show you how to implement a robust email monitoring solution using a Must-Use (MU) plugin.
What You’ll Learn
In this tutorial, you’ll learn how to install email monitoring plugin, track all outgoing emails from your WordPress site, log email failures with detailed error messages, configure SMTP settings monitoring, set up optional heartbeat testing, secure sensitive email credentials, and manage log file rotation automatically.
Why Use an MU-Plugin for Email Monitoring?
Must-Use plugins are perfect for email monitoring because they load automatically before regular plugins, cannot be accidentally deactivated, run independently of your theme, and provide consistent monitoring across your entire site.
Prerequisites
Before you begin, you’ll need access to your WordPress installation files via FTP, SSH, or file manager, the ability to edit wp-config.php for advanced configuration (though this is optional), and a basic understanding of WordPress file structure.
Step 1: Understanding the Plugin Architecture
This email monitor is designed with production environments in mind. On the security front, it includes sanitized log entries to prevent injection attacks, automatic log file rotation to prevent disk space issues, hidden SMTP credentials in logs, and optional body logging that’s disabled by default for privacy. From a performance perspective, it has minimal overhead since hooks only trigger during email sending, uses efficient file locking mechanisms, and has configurable log size limits. For diagnostics, it provides detailed transport information for SMTP, mail, and sendmail, tracks failures with error messages, and offers optional heartbeat testing for proactive monitoring.
Step 2: Installing the Plugin
Create a new file in your WordPress installation at:
wp-content/mu-plugins/email-monitor.php
Note: If the mu-plugins directory doesn’t exist, create it first.
Copy and paste the following code into the file:
<?php
/**
* Plugin Name: Email Monitor
* Description: Production-safe outgoing email logging & diagnostics.
* Version: 1.0.0
*
* This MU plugin logs outgoing email, including failures, PHPMailer configuration,
* and optionally performs heartbeat email tests.
*
* Configure via constants in wp-config.php (examples below).
*/
/* --------------------------------------------------------------------------
CONFIGURATION CONSTANTS (Optional — Define in wp-config.php)
-------------------------------------------------------------------------- */
/**
* Where log file is stored.
* Default: wp-content/email-monitor.log
*/
if (!defined('EMAIL_MONITOR_LOG_FILE')) {
define('EMAIL_MONITOR_LOG_FILE', WP_CONTENT_DIR . '/email-monitor.log');
}
/**
* Maximum log file size in bytes before rotation.
* Default: 500 KB
*/
if (!defined('EMAIL_MONITOR_MAX_LOG_SIZE')) {
define('EMAIL_MONITOR_MAX_LOG_SIZE', 500 * 1024);
}
/**
* Whether to log email body. Default: false (safer).
*/
if (!defined('EMAIL_MONITOR_LOG_BODY')) {
define('EMAIL_MONITOR_LOG_BODY', false);
}
/**
* Enable heartbeat test emails to verify email functionality.
* Default: false
*/
if (!defined('EMAIL_MONITOR_HEARTBEAT')) {
define('EMAIL_MONITOR_HEARTBEAT', false);
}
/**
* Heartbeat interval (hourly, twicedaily, daily). Default: hourly
*/
if (!defined('EMAIL_MONITOR_HEARTBEAT_INTERVAL')) {
define('EMAIL_MONITOR_HEARTBEAT_INTERVAL', 'hourly');
}
/**
* Email address to send heartbeat tests. Defaults to admin_email.
*/
if (!defined('EMAIL_MONITOR_HEARTBEAT_TO')) {
define('EMAIL_MONITOR_HEARTBEAT_TO', false);
}
/* --------------------------------------------------------------------------
INTERNAL UTILITY: SAFE LOGGING & ROTATION
-------------------------------------------------------------------------- */
function email_monitor_safe_log($content) {
$file = EMAIL_MONITOR_LOG_FILE;
// Rotate if file too big
if (file_exists($file) && filesize($file) > EMAIL_MONITOR_MAX_LOG_SIZE) {
@rename($file, $file . '.' . date('Ymd-His') . '.bak');
}
// Write with exclusive lock
$line = $content . "\n";
@file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
}
/* --------------------------------------------------------------------------
MAIN EMAIL LOGGING
-------------------------------------------------------------------------- */
add_action('phpmailer_init', function($phpmailer) {
$to = array_map(
fn($addr) => sanitize_email($addr[0] ?? ''),
$phpmailer->getToAddresses()
);
$log = "---- EMAIL ----\n";
$log .= "Time: " . date('Y-m-d H:i:s') . "\n";
$log .= "To: " . implode(', ', $to) . "\n";
$log .= "Subject: " . sanitize_text_field($phpmailer->Subject) . "\n";
$log .= "Mailer: " . sanitize_text_field($phpmailer->Mailer) . "\n";
// SMTP details (obscure sensitive values)
if ($phpmailer->Mailer === 'smtp') {
$log .= "SMTP Host: " . sanitize_text_field($phpmailer->Host) . "\n";
$log .= "SMTP Port: " . intval($phpmailer->Port) . "\n";
$log .= "SMTP Auth: " . ($phpmailer->SMTPAuth ? 'yes' : 'no') . "\n";
$log .= "SMTP Secure: " . sanitize_text_field($phpmailer->SMTPSecure ?: 'none') . "\n";
$log .= "SMTP Username: " . (!empty($phpmailer->Username) ? '[hidden]' : '[none]') . "\n";
}
if (EMAIL_MONITOR_LOG_BODY) {
$bodySnippet = substr(strip_tags($phpmailer->Body), 0, 5000);
$log .= "Body (truncated):\n" . $bodySnippet . "\n";
}
$log .= "---- END EMAIL ----";
email_monitor_safe_log($log);
});
/* --------------------------------------------------------------------------
FAILURE LOGGING
-------------------------------------------------------------------------- */
add_action('wp_mail_failed', function($error) {
$log = "---- MAIL FAILED ----\n";
$log .= "Time: " . date('Y-m-d H:i:s') . "\n";
$log .= "Error: " . sanitize_text_field($error->get_error_message()) . "\n";
$log .= "---- END FAILURE ----";
email_monitor_safe_log($log);
});
/* --------------------------------------------------------------------------
OPTIONAL HEARTBEAT TEST EMAILS
-------------------------------------------------------------------------- */
if (EMAIL_MONITOR_HEARTBEAT) {
add_action('init', function() {
if (!wp_next_scheduled('email_monitor_heartbeat_event')) {
wp_schedule_event(time(), EMAIL_MONITOR_HEARTBEAT_INTERVAL, 'email_monitor_heartbeat_event');
}
});
add_action('email_monitor_heartbeat_event', function() {
$to = EMAIL_MONITOR_HEARTBEAT_TO ?: get_option('admin_email');
wp_mail(
$to,
'Email Monitor Heartbeat',
"This is a periodic heartbeat email verifying WP mail is functioning.\nTime: " . date('Y-m-d H:i:s')
);
});
}
/* END OF PLUGIN */
That’s it! The plugin is now active and will begin logging all outgoing emails to wp-content/email-monitor.log.
Step 3: Testing the Installation
To verify the plugin is working, send a test email from your WordPress site (you can use the password reset form for this), then check for the log file at wp-content/email-monitor.log. Open the file to see the logged email details.
Example log entry:
---- EMAIL ----
Time: 2025-11-21 14:30:45
To: [email protected]
Subject: Password Reset Request
Mailer: smtp
SMTP Host: smtp.sendgrid.net
SMTP Port: 587
SMTP Auth: yes
SMTP Secure: tls
SMTP Username: [hidden]
---- END EMAIL ----
Step 4: Advanced Configuration
For more control over the plugin’s behavior, you can add configuration constants to your wp-config.php file. Add these before the line that says /* That's all, stop editing! */.
Change the Log File Location
define('EMAIL_MONITOR_LOG_FILE', '/var/log/wp-email-monitor.log');
This is useful if you want to store logs outside your web-accessible directory for added security.
Increase Maximum Log Size
define('EMAIL_MONITOR_MAX_LOG_SIZE', 1024 * 1024); // 1 MB
The default is 500 KB. When the log reaches this size, it automatically rotates with a timestamped backup.
Enable Email Body Logging
define('EMAIL_MONITOR_LOG_BODY', true);
Warning: Only enable this temporarily for debugging. Email bodies may contain sensitive user data.
Enable Heartbeat Testing
define('EMAIL_MONITOR_HEARTBEAT', true);
define('EMAIL_MONITOR_HEARTBEAT_INTERVAL', 'hourly');
This sends periodic test emails to verify your mail system is functioning. Available intervals are hourly, twicedaily, and daily.
Specify Heartbeat Recipient
define('EMAIL_MONITOR_HEARTBEAT_TO', '[email protected]');
By default, heartbeat emails go to the site’s admin email address.
Complete Configuration Example
Here’s a full example configuration for a staging environment:
// Email Monitor Configuration
define('EMAIL_MONITOR_LOG_FILE', WP_CONTENT_DIR . '/logs/email-monitor.log');
define('EMAIL_MONITOR_MAX_LOG_SIZE', 2 * 1024 * 1024); // 2 MB
define('EMAIL_MONITOR_LOG_BODY', true); // Staging only!
define('EMAIL_MONITOR_HEARTBEAT', true);
define('EMAIL_MONITOR_HEARTBEAT_INTERVAL', 'hourly');
define('EMAIL_MONITOR_HEARTBEAT_TO', '[email protected]');
Step 5: Reading and Interpreting Logs
Successful Email Log Entry
---- EMAIL ----
Time: 2025-11-21 15:22:10
To: [email protected], [email protected]
Subject: Order Confirmation #12345
Mailer: smtp
SMTP Host: smtp.mailgun.org
SMTP Port: 587
SMTP Auth: yes
SMTP Secure: tls
SMTP Username: [hidden]
---- END EMAIL ----
This shows a successful email sent via SMTP with TLS encryption on port 587.
Failed Email Log Entry
---- MAIL FAILED ----
Time: 2025-11-21 15:25:33
Error: SMTP Error: Could not authenticate.
---- END FAILURE ----
This indicates an SMTP authentication problem—check your SMTP credentials.
Step 6: Troubleshooting Common Issues
Problem: No Log File Created
Solution: Check file permissions on the wp-content directory. The web server needs write permissions.
chmod 755 wp-content
Problem: Log File Too Large
Solution: The plugin automatically rotates logs, but you can manually delete old backup files:
rm wp-content/email-monitor.log.*.bak
Problem: SMTP Credentials Not Working
Solution: Look for these common issues in your logs: wrong hostname (check the SMTP Host value), wrong port (usually 587 for TLS, 465 for SSL, 25 for unencrypted), authentication disabled when required (check the SMTP Auth value), or incorrect security protocol (check the SMTP Secure value).
Problem: Emails Sent But Not Delivered
Solution: This plugin logs sending attempts, not delivery. Check your SMTP provider’s logs, recipient spam folders, and SPF, DKIM, and DMARC records for your domain.
Understanding Log Rotation
The plugin automatically manages log file size to prevent disk space issues. Before each write, the plugin checks the log file size, and if it exceeds EMAIL_MONITOR_MAX_LOG_SIZE, the current log is renamed with a timestamp (for example, email-monitor.log becomes email-monitor.log.20251121-152230.bak), and a new log file is created for fresh entries. This ensures your logs never grow unbounded while preserving historical data.
Security Best Practices
Protect Your Log Files
Add this to your .htaccess file to prevent direct access to log files:
<FilesMatch "\.log$">
Order allow,deny
Deny from all
</FilesMatch>
Don’t Log Email Bodies in Production
Email bodies can contain user passwords in reset emails, personal information, payment details, and private correspondence. Only enable EMAIL_MONITOR_LOG_BODY temporarily for debugging specific issues.
Regularly Review Logs
Set up a cron job or scheduled task to review logs for unusual spikes in failed emails, authentication errors, and repeated delivery failures.
Rotate Logs Externally
For high-volume sites, consider using logrotate or similar tools:
/path/to/wp-content/email-monitor.log {
daily
rotate 7
compress
missingok
notifempty
}
Advanced Use Cases
Monitoring Multiple Sites
If you manage multiple WordPress sites, standardize the configuration across all sites:
// In each site's wp-config.php
define('EMAIL_MONITOR_LOG_FILE', '/var/log/wp-email/' . $_SERVER['HTTP_HOST'] . '.log');
define('EMAIL_MONITOR_HEARTBEAT', true);
define('EMAIL_MONITOR_HEARTBEAT_TO', '[email protected]');
This creates separate log files for each domain and centralizes heartbeat notifications.
Integration with Monitoring Tools
Parse the log files with your monitoring solution. Example for New Relic:
// Add to the plugin after logging
if (extension_loaded('newrelic')) {
newrelic_notice_error('WP Mail Failed: ' . $error->get_error_message());
}
Debugging Transactional Emails
When troubleshooting plugin-specific emails, enable body logging temporarily, then trigger the specific action such as placing an order or submitting a form. Check the log for the email details, then disable body logging when you’re done.
Performance Considerations
This plugin has minimal performance impact because it only runs during email sending rather than on every page load, file operations use efficient locking mechanisms, log rotation happens only when necessary, and no database queries are performed. On a typical WordPress site sending 100 emails per day, you won’t notice any performance difference.
When to Use This Plugin
This plugin is ideal for debugging email delivery issues, monitoring critical transactional emails, verifying SMTP configuration, tracking email volume and patterns, and ensuring email functionality in production. It’s not necessary for sites with reliable email delivery and no issues, sites using enterprise email services with built-in logging, or development environments with email capture tools like MailHog.
Removing the Plugin
If you need to remove the plugin, simply delete the file at wp-content/mu-plugins/email-monitor.php. You can optionally delete the log file at wp-content/email-monitor.log as well. If you enabled heartbeat testing, the cron job will automatically clear after a few hours.
Next Steps: Adding an Admin Interface
The current implementation logs to a file, which is perfect for developers with server access. If you’d like to add a WordPress admin interface for viewing logs, you could extend the plugin with a secure admin page restricted to administrators, log viewing with pagination, one-click log clearing, the ability to download logs as a text file, and a real-time email testing interface. This would make the plugin accessible to non-technical administrators while maintaining security through proper capability checks.
You now have a production ready email monitoring solution that provides complete visibility into your WordPress email delivery, automatic log management with rotation, security-first design with sanitized outputs, optional proactive monitoring with heartbeat tests, zero configuration required since it works out of the box, and full customization through configuration constants.
The plugin will run silently in the background, logging every email your site sends and capturing any failures with detailed error messages. This makes diagnosing email issues straightforward and helps ensure your critical notifications reach their intended recipients.
Remember to check your logs periodically, especially after making changes to your email configuration or SMTP settings. Happy monitoring!