Adding images to e-mails

WordPress gives you complete access to its use of the PHPMailer class / object, through the ‘phpmailer_init’ action.

I use this action all the time, for the sole purpose of sending e-mails from development machines which otherwise wouldn’t send without tedious configuration. I use the WP docs’ example pretty much verbatim:

add_action( 'phpmailer_init', 'my_phpmailer_example' );
function my_phpmailer_example( $phpmailer ) {
    $phpmailer->isSMTP();     
    $phpmailer->Host = 'smtp.example.com';
    $phpmailer->SMTPAuth = true; // Force it to use Username and Password to authenticate
    $phpmailer->Port = 25;
    $phpmailer->Username = 'yourusername';
    $phpmailer->Password = 'yourpassword';
}

… of course I never get it right the first time, so I add $phpmailer->SMTPDebug = 2; to output errors until it’s fixed.

If you’re familiar with PHPMailer — and you should get familiar if you’re not — you know there are all kinds of wonderful things you can do with it. One of them is adding inline images; another is adding alternate HTML / plain text content.

Maybe you’re used to including images by simply adding an HTML tag with your image’s URL as its src attribute. There is some debate, but I dislike the img-tag approach for a few reasons:

  • Your SPAM score increases (making it more likely your messages will get SPAM-trapped, and in the worst cases, your domain can get blacklisted).
  • Many e-mail clients block remote images by default (because of a well-known malware trick)
  • If your image URL changes or your site goes down temporarily, users won’t see it.

I should really say here: the best way to include images in your e-mails is not to include them at all. But if you absolutely must, and you agree with my reasons above, then PHPMailer provides the “right” way to do it. Be sure to use images sparingly, and optimize as much as possible (to minimize message size). Also, don’t rely on users reading important information solely from an image — many users will only see your plain-text message no matter which method you choose.

First, hook into the ‘wp_mail’ filter, which acts upon outgoing messages before they’re sent to PHPMailer. This simply transforms the default plain-text message to an HTML-formatted one:

add_filter('wp_mail', 'wpcx_format_user_emails');
function wpcx_format_user_emails($vars) {
	$vars['message'] = '<p><img title="My WP Site" alt="My WP Site" src="cid:an_email_header" /></p>
	<p>' . $vars['message'] . '</p>';
	return $vars;
}

The important part here is, the src attribute, src="cid:an_email_header". The name following cid: can be anything you like; you’ll repeat it in the next function.

Incidentally, the $vars array also contains ‘to’, ‘subject’, and ‘headers’ elements. It contains an ‘attachments’ element too, which merely enables you to attach images (and files) without positioning them within the message — we’ll leave that untouched.

After the wp_mail filter has done its job, WP initializes a PHPMailer object and makes it available via an action call.

add_action('phpmailer_init','wpcx_init_phpmailer');
function wpcx_init_phpmailer($phpmailer) {
	$headpath = get_stylesheet_directory() . '/images/emails/email-header-682x85.jpg';
	$phpmailer->AddEmbeddedImage($headpath,'an_email_header','email-header-682x85.jpg');
	$phpmailer->AltBody = strip_tags($phpmailer->Body);
}

The $headpath must be the filepath (not the URI). Notice the second argument of the AddEmbeddedImage method is the name you made up in the previous function. Here we’ve also added a plain-text alternate of the HTML-formatted message using strip_tags. Incidentally, when you define an AltBody property, PHPMailer automatically formats the message as multipart, HTML preferred.

You may notice this hooked action adds an image to all messages, including procedural admin messages. How can you add the image only upon demand? One simple way is to define a global variable — be sure it has a complex name that couldn’t likely be repeated somewhere else. Set this variable true just before you call wp_mail:

global $xxyyzz_use_email_image_header;
$xxyyzz_use_email_image_header = true;
wp_mail( …

Then, add these lines first, in your phpmailer hook function:

function wpcx_init_phpmailer($phpmailer) {
	global $xxyyzz_use_email_image_header;
	if (!isset($xxyyzz_use_email_image_header) || !$xxyyzz_use_email_image_header) return;
	…

This “bails” on the function if the variable is not defined, or defined but set to an untrue value (0, false, ”, null).

Since you may find you’ll need to define many global variables within hooks & filters, why not define a single global array variable (with a complex name) and define keys within the array?

	global $xxyyzz_global_registers;
	if (!isset($xxyyzz_global_registers['use_email_image']) || !$xxyyzz_global_registers['use_email_image']) return;
	…