Getting error feedback from `wp_mail`
Have you ever had to debug WordPress email problems, like mail not being sent, without any apparent reason? The wp_mail
function is usually used to send emails from within WordPress code. This is a pluggable function and, thus, resides in wp-includes/pluggable.php.
Its default behavior prevents it from returning anything but false
when sending fails for one reason or another. And that often times poses a problem when trying to debug wp_mail
issues.
ticket #18926 in WordPress Trac was opened with a request to solve this problem. Here are some thoughts and possible solutions.
Returning the exception
try { $phpmailer->Send(); } catch ( phpmailerException $e ) { return $e; }
This looks valid, however, and for more consistency one would use the core WP_Error
object, like so:
try { $phpmailer->Send(); } catch ( phpmailerException $e ) { return new WP_Error( $e->getCode(), $e->getMessage() ); }
However, plugins and themes using wp_mail
will probably start to break. These have been expecting a boolean return values for years. And although new plugins would check for is_wp_error
, older plugins would break, since any object will evaluate to true
. Plugins will think everything did go pretty well… or did it?!
Probably not a good idea, we’re in too deep. Maybe some day, when PHP receives the gift or curse of operator overloading…
Throwing an exception
try { $phpmailer->Send(); } catch ( phpmailerException $e ) { throw $e; return false; }
…not unless you’d like to see “Fatal error: Uncaught exception ‘phpmailerException’ with message…” every time the function fails, because no single call to the original unplugged wp_mail
in any plugin or theme so far is ever wrapped in try... ...catch
.
Thus, with this solution as well, we hit back-compatibility issues.
Of course, by setting your own exception handler callback you’d be able to catch all the newly created uncaught exceptions. Why create them in the first place? Doesn’t seem to be too good of a solution either.
Add a hook
try { $phpmailer->Send(); } catch ( phpmailerException $e ) { do_action_ref_array( 'php_mailer_failed', array( &$phpmailer ) ); return false; }
(The function decides whether an arguments is passed by reference or not, the code will throw a “deprecated” warning as of PHP 5.3)
This is the current diff
that is available in the ticket, and doesn’t look too good, in my opinion. I can’t seem to find a clean and straightforward branching technique that can be used.
add_action( 'php_mailer_failed', function( &$phpmailer ) { do_something_with( $phpmailer->ErrorInfo ); }); if ( !wp_mail(...) ) // start making noise else { } // we're good to go
A bit bulky to use. How about:
try { $phpmailer->Send(); } catch ( phpmailerException $e ) { $mail_error = new WP_Error( $e->getCode(), $e->getMessage() ); return apply_filters( 'wp_mail_send_failed', false, $mail_error ); }
This will act like a switch:
add_filter( 'wp_mail_send_failed', function( $false, &$mail_error ) { return $mail_error; /* switch on mail error return */ }, 10, 2 ); if ( is_wp_error(wp_mail(...)) ) // start making noise else { } // yippie!
Looks bulky too, besides you’d have to remove the filter after using it due to the same reasons described in the “Returning an exception” section above.
Using the global
$phpmailer
is an initialized global (when wp_mail
is unplugged) and has a public ErrorInfo
property.
if ( !wp_mail(...) ) do_something_with( $GLOBALS['phpmailer']->ErrorInfo ); else { }
Of course, $phpmailer
could be overridden by some other piece of code, so always check before using.
Pluggin the function
The wp_mail
function is quite large, but can be plugged. However, the interface that it provides:
* @param string|array $to Array or comma-separated list of email addresses to send message. * @param string $subject Email subject * @param string $message Message contents * @param string|array $headers Optional. Additional headers. * @param string|array $attachments Optional. Files to attach. * @return bool Whether the email contents were sent successfully.
…has to be complied with, otherwise other themes and plugins may break. This is always important when plugging functions.
So…
…are we stuck with something that returns false
with no general way out? Are there any other valid solutions or methods that can help drill down towards more feedback from wp_mail
?
What about:
$result = wp_mail($sendto, $subject, $content, $headers);
if (!$result) {
global $ts_mail_errors;
global $phpmailer;
if (!isset($ts_mail_errors)) $ts_mail_errors = array();
if (isset($phpmailer)) {
$ts_mail_errors[] = $phpmailer->ErrorInfo;
}
}
😉
Yep, that probably works, thanks for the tip 🙂