'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; ?>

Send test email

Uses wp_mail() with the default From address. The debug option turns on PHPMailer SMTPDebug=2 for this one send — useful when an SMTP plugin is routing the mail; no effect when transport is plain PHP mail(). Credentials in AUTH lines are redacted.

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)' : '') ); } };