'WP Mail SMTP',
'fluent-smtp/fluent-smtp.php' => 'FluentSMTP',
'post-smtp/postman-smtp.php' => 'Post SMTP',
'easy-wp-smtp/easy-wp-smtp.php' => 'Easy WP SMTP',
'gmail-smtp/main.php' => 'Gmail SMTP',
'wp-ses/wp-ses.php' => 'WP Offload SES',
'sendgrid-email-delivery-simplified/wpsendgrid.php' => 'SendGrid',
];
$found = [];
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
foreach ($smtp_plugins as $file => $label) {
if (is_plugin_active($file)) $found[] = $label;
}
$f[] = $this->finding(
'mailer',
$found ? 'ok' : 'info',
'Mailer',
$found ? implode(', ', $found) : 'PHP mail() (default)',
$found ? 'Test will route through this plugin.' : 'No SMTP plugin detected — wp_mail will use PHP\'s built-in mail() function, which is often blocked on shared hosts.'
);
// Default From address (filter into wp_mail_from)
$from = apply_filters('wp_mail_from', '');
if (!$from) {
$sitename = parse_url(get_site_url(), PHP_URL_HOST) ?: 'localhost';
$sitename = preg_replace('/^www\./', '', strtolower($sitename));
$from = 'wordpress@' . $sitename;
}
$f[] = $this->finding('from_address', 'info', 'Default From', $from, 'wp_mail_from filter value.');
return $f;
}
/** Render the send-test form inside the step card. */
public function render_extra(array $session_state): void {
$current = wp_get_current_user();
$default_to = $current && $current->user_email ? $current->user_email : get_option('admin_email');
$debug_data = $session_state['email_debug'] ?? null;
?>
SMTP debug trace — transport: · captured ago
SMTP debug was requested but no trace lines were captured — transport was . PHPMailer only emits trace output for the smtp transport.
finding('last_send', 'bad', 'Last test send', 'invalid address', 'The destination address was not a valid email.');
}
$debug_requested = !empty($input['debug']);
// Capture any wp_mail_failed error so we can surface the underlying reason.
$error_msg = '';
$capture_error = function ($wp_error) use (&$error_msg) {
if (is_object($wp_error) && method_exists($wp_error, 'get_error_message')) {
$error_msg = (string) $wp_error->get_error_message();
}
};
add_action('wp_mail_failed', $capture_error);
// Optional: turn on PHPMailer SMTP debug for this one send. Only useful
// when transport is 'smtp' — for plain PHP mail() there is no wire
// conversation to capture.
$trace = [];
$transport = 'mail';
$debug_hook = null;
if ($debug_requested) {
$debug_hook = function ($phpmailer) use (&$trace, &$transport) {
$transport = isset($phpmailer->Mailer) ? (string) $phpmailer->Mailer : 'mail';
$phpmailer->SMTPDebug = 2; // 0=off, 1=client, 2=client+server, 3=connection, 4=low-level
$phpmailer->Debugoutput = function ($line, $level) use (&$trace) {
// Redact AUTH credentials — PHPMailer emits the base64'd username/password
// when SMTPDebug captures the AUTH LOGIN handshake.
if (stripos($line, 'AUTH') !== false || preg_match('/^[A-Za-z0-9+\/=]{16,}$/', trim($line))) {
$line = '[REDACTED — credential or response containing it]';
}
$trace[] = trim($line);
};
};
add_action('phpmailer_init', $debug_hook, 1000);
}
$host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site';
$subject = '[Site Healthcheck] Test from ' . $host . ' — ' . date('Y-m-d H:i');
$body = "This is a test email sent from the Site Healthcheck plugin.\n\n"
. "Site: " . get_site_url() . "\n"
. "Sent: " . date('Y-m-d H:i:s') . "\n"
. "Technician: " . (wp_get_current_user()->user_login ?? '?') . "\n\n"
. "If you received this, wp_mail() is working from this site.";
$sent = wp_mail($to, $subject, $body);
remove_action('wp_mail_failed', $capture_error);
if ($debug_hook) remove_action('phpmailer_init', $debug_hook, 1000);
// Stash the debug trace on the session so the UI can render it as a
// collapsible block. Returning it inside the finding's detail would
// mangle the formatting.
$session = WPH_Session::current();
if ($session && $debug_requested) {
$trace = array_slice($trace, 0, 200); // cap to avoid bloating the option
$data = $session->data();
$data['email_debug'] = [
'captured_at' => time(),
'transport' => $transport,
'trace' => $trace,
];
update_option(WPH_OPT_SESSION, $data, false);
// Append the trace to the step's notes so the technician can edit
// it and so it lands in the downloadable report (notes are
// already serialised verbatim).
$current_state = $session->step_state($this->id());
$current_notes = (string) $current_state['notes'];
$stamp = date('Y-m-d H:i');
$block = "\n\n--- SMTP debug trace (" . $stamp . ", transport: " . $transport . ", to: " . $to . ") ---\n";
$block .= $trace ? implode("\n", $trace) : '(no trace lines captured — transport does not emit SMTP traffic)';
$block .= "\n--- end trace ---";
// Keep whatever status the technician had previously chosen.
$session->update_step($this->id(), (string) $current_state['status'], trim($current_notes . $block));
}
$time = date('Y-m-d H:i');
if ($sent) {
$detail = 'wp_mail() returned true — the transport accepted handoff. This does not guarantee inbox delivery; confirm receipt (check spam too).';
if ($debug_requested) {
$detail .= ' SMTP debug ' . ($transport === 'smtp' ? 'captured below — ' . count($trace) . ' line(s).' : 'requested but transport is "' . $transport . '" (no SMTP conversation to capture).');
}
return $this->finding('last_send', 'ok', 'Last test send', 'sent to ' . $to . ' at ' . $time, $detail);
}
return $this->finding(
'last_send',
'bad',
'Last test send',
'failed at ' . $time,
($error_msg ?: 'wp_mail() returned false but no error was captured. Check the host\'s mail logs.')
. ($debug_requested && $trace ? ' (debug trace below)' : '')
);
}
};