How To Find Out The Source Of Spam Emails On Your WordPress Site

The most common spam problem with WordPress sites is incoming spam. It usually reaches your comment and contact forms. There are various plugins and services that help you reduce that spam. I won’t deal with that issue here.

A not less delicate problem, however, is spam that is sent from your site. Your site might have been hacked or one of your plugins or themes has a security bug. A symptom is a huge number of mail being sent out from your site. Your hosting company or system admin might therefore send you a warning. Or they may even block the PHP function to send out mails. If you receive an undeliverable mail from your site, you can often clearly identify it as spam.

Of course, you first need to identify the precise location of the cause before you can fix it. And here hosting companies often don’t give you much useful information.

Log emails sent by WordPress

I therefore created a short script that can you can paste for example into the functions.php of your active (child) theme. It doesn’t solve any problems. It only helps you debug. It intercepts the WordPress wp-mail function and logs the output to a file. After each entry it adds a backtrace that shows you what happened before the mail was sent.

The size of the output file is limited to 50 KB. You could also forward these emails to your own address but I prefer to write them to a file in case the webhosting has disabled sending mails.

function cm_log_mail( $variables ) {

  $upload_dir = wp_upload_dir();

  if ( ! empty( $upload_dir['basedir'] ) ) {

    $path_to_file = $upload_dir['basedir'] . '/mails-log.txt'; // or choose your own file name

    if ( filesize( $path_to_file ) < 1024 * 50 ) { // set here the max size

      ob_start();
      debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // remove DEBUG_BACKTRACE_IGNORE_ARGS to log all parameters, including mail bodies, which can be large
      $trace = ob_get_contents();
      ob_end_clean();

      $text =  "nn";
      $text .= 'To: ' . $variables['to'] . "n";
      $text .= 'Subject: ' . $variables['subject'] . "n";
      $text .= 'Backtrace: ' . $trace . "n";

      $text .= '===========================' . "n";

      file_put_contents( $path_to_file, $text, FILE_APPEND );

    }

  }

}

add_filter( 'wp_mail', 'cm_log_mail', 1 );

The output can be viewed through an URL. (That’s why you need to disable the additional lines and remove the log file after you finished debugging.)

https://www.example.com/wp-content/uploads/mails-log.txt

Here is some sample output after I sent a test mail with WP Mail SMTP. The important things happen starting with the line “wp_mail() called at ...“.((The backtrace is ordered chronologically with the oldest events at the bottom.))


To: chattymango@example.com
Subject: WP Mail SMTP: HTML Test email to chattymango@example.com
Backtrace: #0  catch_mail() called at [/path-to-wp/wp-includes/class-wp-hook.php:286]
#1  WP_Hook->apply_filters() called at [/path-to-wp/wp-includes/plugin.php:208]
#2  apply_filters() called at [/path-to-wp/wp-includes/pluggable.php:186]
#3  wp_mail() called at [/path-to-wp/wp-content/plugins/wp-mail-smtp/src/Admin/Pages/Test.php:167]
#4  WPMailSMTPAdminPagesTest->process_post() called at [/path-to-wp/wp-content/plugins/wp-mail-smtp/src/Admin/Area.php:646]
#5  WPMailSMTPAdminArea->process_actions() called at [/path-to-wp/wp-includes/class-wp-hook.php:286]
#6  WP_Hook->apply_filters() called at [/path-to-wp/wp-includes/class-wp-hook.php:310]
#7  WP_Hook->do_action() called at [/path-to-wp/wp-includes/plugin.php:465]
#8  do_action() called at [/path-to-wp/wp-admin/admin.php:169]

===========================

You see that sending was triggered somewhere at .../plugins/wp-mail-smtp/... and you can even check the precise code. The numbers behind the names of PHP files are line numbers.

Of course, this script only logs activities that make use of wp-mail. If you don’t see anything suspicious here, something might still use PHP’s mail or PHPMailer directly.((Then it becomes more complicated. Check out Xdebug.))

Don’t forget to remove the debugging information

Since the file mails-log.txt (or however you named it) is publicly accessible, you should remove the code from the functions.php (or comment out the add_filter part) after you’re done debugging and then delete the log file too.

Photo by Christoph Amthor

Still no luck? Let an expert fix it!