Hacking WordPress Emails


E-mail Box
November 2013

Contents



If you have a WordPress site, you've no doubt received email messages from "WordPress". If you've also registered with other WordPress based sites, you've probably received more emails, all from WordPress, though the emails were from a variety of sites. Obviously, site owners do not know how to alter this default setting so that messages from their site can be uniquely identified. There's very little excuse for this, it's quite easy to change this, though it does involve a small amount of coding. (OK, that's actually a decent excuse) The necessary coding can be mostly cut and paste from right here. And while we're at it, we'll look at all the other things you can do to change how WordPress does emails.

From Name


Getting WordPress emails to use a From: name of your choosing doesn't get much easier. (at least until WordPress developers decide to put the option on an admin screen) Copy the following snippet to your theme's (or better yet, child theme'sLink is to outside of this site) functions.php file. Edit the code to contain the name of your choosing, then upload the file to your server. Job done!
function bcw_from_name() { 
	return "Your Site's Name Here"; // or whatever you want emails to be From:
} 
add_filter('wp_mail_from_name', 'bcw_from_name');
How does this code work? It uses a fundamental technique to hack WordPress known as 'hooks'Link is to outside of this site. The add_filter() call registers our callback function bcw_from_name() so it is called any time WordPress needs an email From: name. WordPress then uses what ever value is returned from the callback.


From Address


The default From: email address of wordpress@yourdomain.com might be acceptable to you, but if not, changing the From: address is just as easy as changing the From: name. Do the same thing you did for the From: name above with the snippet below, except this time enter the return email address.

function bcw_from_email() { 
	return 'YourEmail@yourdomain.com'; 
} 
add_filter('wp_mail_from', 'bcw_from_email');
to Contents

More Email Hacks


You can do much more with WordPress emails than change the From: fields. In fact, the possibilities are wide open. Anything you can do with email can be done in WordPress. There are a number of filters you can hook into to alter the various parameters involved with sending an email message. There is also an action hook with which you can alter any aspect of the mailer object. Finally, most of the WordPress functions that send notifications such as new user registration are pluggable. Pluggable functions can be redefined by your plugin or child theme. Your redefined version will be used in place of the default version. (Normally in PHP, attempting to redefine a function causes a fatal error.)

It's a good idea to test any email hacks by sending email messages to yourself before letting such messages go out to users. Otherwise you have no idea if your hacks were executed correctly. It's all too easy to make some silly mistake causing the whole thing to fail. The following is a simple script that replaces the To: field of any WordPress email with that of your own email address for testing. The add_filter() line is currently commented out so that the script is non-operational. Simply remove the double slash // to enable the script and reinsert again to disable. (Upload to your server each time of course) It becomes a sort of switch for any time you need to test mail functions.
//add_filter('wp_mail', function( $parms ){ $parms['to'] ='yourmailname@yourmailprovider.com'; return $parms; });

Filters


Any mail filters you add will be applied to all calls to wp_mail(). If you add any filters to handle a specific wp_mail() call, you should immediately remove those same filters after the call to avoid corrupting other calls that should not have your filters applied. Example:
add_filter('wp_mail_content_type', 'bcw_set_html_content_type');
wp_mail( $multiple_to_recipients, 'The subject', '<p>The <em>HTML</em> message</p>');
// Reset content-type to avoid conflicts— http://core.trac.wordpress.org/ticket/23578
remove_filter('wp_mail_content_type', 'bcw_set_html_content_type');

function bcw_set_html_content_type() {
        return 'text/html';
}
'wp_mail_from_name'
'wp_mail_from'
These two filters were covered in the first section of this article, there's little else to say about them.

'wp_mail'
This filter gives you the ability to alter any of the parameters passed to the wp_mail()Link is to outside of this site function. Here is a typical wp_mail() call with default parameter names:
wp_mail( $to, $subject, $message, $headers, $attachments );
Water Filters The parameters are compacted into an array and passed to your filter callback, thus each parameter becomes the key in the passed array. You may change them as desired before returning the array. Review the above linked reference and the following examples for more information on how to use each parameter. You will see $headers is used to specify From:, Cc:, and Bcc: fields, the other parameters are self explanatory.

Since arrays are typically easier to work with than strings, most examples here convert any existing string parameters to an array before adding new elements.

Example code to include 2 file attachments if Subject: has the word "attached":
function bcw_add_attachment( $parms ) {
	if ( stripos( $parms['subject'], 'attached')) {
		//convert any possible delimited string format to array
		if ( !is_array( $parms['attachments'])) 
			$parms['attachments'] = array_filter( array_map('trim', 
				preg_split("/[\n\r\t,]+/", $parms['attachments'])));
		$parms['attachments'][] = WP_CONTENT_DIR . '/uploads/2013/11/sample1.jpg';
		$parms['attachments'][] = WP_CONTENT_DIR . '/uploads/2013/11/sample2.jpg';
	}
	return $parms;
}
add_filter('wp_mail', 'bcw_add_attachment');
Explanation of the array_filter — array_map — preg_split sequence. The string is supposed to be newline separated, but there's a chance it may be return, comma, or tab separated as well. We use a RegExpLink is to outside of this site to split up the string regardless of the delimiter used. The split up string is returned as an array with each string fragment as an element. This array is then fed to the array_map() function to trim off any leading or trailing spaces left over from the splitting process. The trimmed elements could still contain empty strings after this, especially if the string fragments were separated by Windows style line feeds "\r\n". Using array_filter() without a callback removes all empty strings.

In the following examples, assume the $emails array is structured like so:
$emails = array(
	array('name' => 'John Smith', 'address' => 'jsmith@yahoo.c0m',),
	array('name' => 'Adam Jones', 'address' => 'ajones@gmail.c0m',),
	// etc....
)
The .com in the above array definition is intentionally misspelled in order to foil email address scraper bots.
Example code to add Bcc: addresses to an email message:
function bcw_add_attachment( $parms ) {
	//assume $emails is a global array of valid email data
	global $emails;
	//convert any possible string content to an array
	if ( !is_array( $parms['headers'])) 
		$parms['headers'] = array_filter( array_map('trim', explode(',', $parms['headers'])));
	foreach ( $emails as $email ) {
		$parms['headers'][] = "Bcc: {$email['address']}";
	}
	return $parms;
}
add_filter('wp_mail', 'bcw_add_attachment');
to Contents

At the time of this writing, WordPress only passes single email addresses as the $to parameter, so adding additional addresses can be done by simply concatenating a comma and another address to the $mail['to'] value. WordPress makes individual calls to wp_mail() in a loop anytime there is more than one recipient. In such a case, it's overkill to convert the existing value to an array. You could thus add the $emails array of addresses like so:
//assume $emails is an array of valid email addresses and
//  $parms was the passed parameters from wp_mail()
foreach ( $emails as $email ) {
	$parms['to'] .= ",{$email['address']}"
}
In order to future-proof your script and be compatible with other plugins, you should still assume the value could be any one of the valid formats: an array, a single address, or a comma delimited list (with and/or without spaces, extra commas, etc.) and account for all possibilities before adding addresses.
//assume $emails is an array of valid email addresses and
//  $parms was the passed parameters from wp_mail()
if ( !is_array( $parms['to']))
	$parms['to'] = array_filter( array_map('trim', explode(',', $parms['to'])));
foreach ( $emails as $email ) {
	$parms['to'][] = $email['address'];
}
The functions used to split up any string format are similar to the attachments example above, except a comma delimited string is the only acceptable format, so we can use the more efficient explode() instead of the more flexible preg_split() used for attachments. If $emails had been a simple one dimensional array of email addresses, we could have simply merged the converted array and $emails instead of running a foreach loop.

Most mail servers have limits on the number of recipients or the character count of the list, as well as attachment size limits and possibly other restrictions. These limitations must be considered when adding large lists or files.

For what it's worth, the other fields also default to a string, except $attachments which is an empty array by default but may have a CRLF (\r\n) separated list string from some other filter hook. Of course, $subject and $message must be a string but other parameters could be either a string or an array.

'wp_mail_content_type'
This filter allows you to change the mime type from the default 'text/plain' to any other valid mime type. The one type most people might want to use is 'text/html' for formatted text, but you can set it to anything you need. When you alter the default by adding your callback to this filter, you must then remove your callback after you've made the wp_mail() call to avoid conflicts with system calls that require the default content type. The introductory example of this section illustrates this technique.

'wp_mail_charset'
The charset of emails is set by default to that of the blog, usually UTF-8, but you can use this filter to set the charset to any valid charset string. If you change it from that of the blog, you must then ensure the message string uses the encoding specified. The functions mb_convert_encoding()Link is to outside of this site or iconv()Link is to outside of this site can be used for changing encodings. As mentioned earlier, you should remove your filter callback when you're done using it to avoid conflicts with other system mail functions.

All the above filters are invoked by the wp_mail() function. The pluggable functions also have several filters that you can hook on to. Their filters are listed with each function below.
to Contents

Actions


'phpmailer_init'
Action Figure Your last chance to alter the $phpmailer object before the Send() method is called, passing the message on to the mail server. The object is passed by reference to your action callback so you have complete, direct control of the email configuration via this action.

PHPMailer


PHPMailerLink is to outside of this site is a GPL licensed email transfer class used by WordPress. On the initial call to wp_mail(), a global $phpmailer object is created. wp_mail() causes $wpmailer to use PHP's mail() function to send email. This (and anything else about the class object) can be changed by hooking the 'phpmailer_init' action and changing the PHPMailer object passed by reference.

Pluggables

(mail related)
You can define your own pluggable function of the same name and it will be used instead of the core version. With all the other ways you can change how things work via action and filter hooks there is little reason to create you own version, but you can if you do happen to have a reason. These are the email related pluggable functions and the filters available to tweak the data without wholesale redefinition of the entire function.

If you do find the need to redefine a function, it's a good idea to retain any apply_filters() calls that are still applicable to maintain maximum compatibility with other plugins the user may have installed.

Notify Author (of comment)
wp_notify_postauthor( $comment_id, $comment_type )Link is to outside of this site
Filters
While you could redefine this function, there is rarely any need since you can change most aspects of what this function does with the following filters.
Filter Tag			Passed parameters

'comment_notification_text' $notify_message, $comment_id 'comment_notification_subject' $subject, $comment_id 'comment_notification_headers' $message_headers, $comment_id
Notify Moderator (of comment)
wp_notify_moderator( $comment_id )Link is to outside of this site
Filters
While you could redefine this function, there is rarely any need since you can change most aspects of what this function does with the following filters.
Filter Tag			Passed parameters

'comment_moderation_text' $notify_message, $comment_id 'comment_moderation_subject' $subject, $comment_id 'comment_moderation_headers' $message_headers
Change Password
wp_password_change_notification()Link is to outside of this site
This function has no filters or actions to hook, though it calls wp_mail() which does.

New User
wp_new_user_notification()Link is to outside of this site
This function has no filters or actions to hook, though it calls wp_mail() which does.

Send Email
wp_mail()Link is to outside of this site
Techniques to change how this function works via actions and filters have been covered. If that's not enough, this function is pluggable as well. If you do plug in your own version, you'd be well advised to continue to use the PHPMailer class, or lacking that, at least some other mail library such as SwiftMailerLink is to outside of this site or Zend/MailLink is to outside of this site. Correctly using PHP's mail() function directly to generate email in full compliance with all standards is exceedingly difficult. Let one of these libraries do it for you.
to Contents
«Back


Comments, feedback, and questions are always welcome. Email me at JavaScript needs to be enabled .