8

I want to add an additional header to outgoing emails on my sites, so I can easily determine which site sent a given email. (I put a standard functionality plugin on all of my sites, so this is easy enough to do, and configuring my email client to filter and otherwise act on this header would be an amazing time-saver.)

I thought this would be a simple matter of plugging into the wp_mail function, but evidently it isn't.

First, I tried this:

add_filter('wp_mail', 'ws_add_site_header');
function ws_add_site_header() {
    return array('headers' => 'X-WU-Site: ' . parse_url(get_site_url(), PHP_URL_HOST));
}

Here, my array took precedence over anything else that modified the mail settings (say, Gravity Forms), so HTML emails started showing up as bare HTML and not nicely-formatted. Makes sense, since the Content-type: header that GF added was scrubbed. But my header was added to the email. Since others also depend on receiving pretty emails (all my sites' content developers and end-users, among others I'm sure), that isn't acceptable.

One of my colleagues then suggested routing my stuff through wp_parse_args(), thus:

add_filter('wp_mail', 'ws_add_site_header');
function ws_add_site_header($args) {
    $new_header = array('headers' => 'X-WU-Site: ' . parse_url(get_site_url(), PHP_URL_HOST));
    return wp_parse_args($args, $new_header);
}

With that, it's as if my function doesn't exist -- my header isn't added, but neither are others' headers and mail settings stripped.

What's the correct way to add a header to an outgoing email, without scrambling up any other filters that might exist?

2
  • If you would like simple solution, please use Email Header Footer Free Wordpress Plugin Dec 4, 2016 at 3:00
  • That plugin doesn't actually address the problem. It adds a "header" to the body of emails generated by WordPress; I was trying to add an SMTP header, which is a completely different thing. Oct 26, 2017 at 16:51

3 Answers 3

7

Thanks to the above, I've realized my central mistake -- I didn't quite realize that the arguments being passed in were a multi-dimensional array.

For now, I've re-implemented the function thus:

function ws_add_site_header($email) {
    $email['headers'][] = 'X-WU-Site: ' . parse_url(get_site_url(), PHP_URL_HOST) ;
    return $email;               
}

My reading of the wp_mail() source (see: https://core.trac.wordpress.org/browser/tags/4.4.2/src/wp-includes/pluggable.php#L235) leads me to believe that the headers component can be an array, or a big string, or possibly some horrific mish-mash of the two, but that using an array is probably the safer/more correct option.

I do like the various phpmailer answers, but it just seems a bit cleaner to try to do things using WordPress' built-ins.

6

Here's an alternative using directly the AddCustomHeader method of the PHPMailer instance:

/**
 * Add a custom header.
 * $name value can be overloaded to contain
 * both header name and value (name:value)
 * @access public
 * @param string $name Custom header name
 * @param string $value Header value
 * @return void
 */
public function addCustomHeader($name, $value = null)
{
    if ($value === null) {
        // Value passed in as name:value
        $this->CustomHeader[] = explode(':', $name, 2);
    } else {
        $this->CustomHeader[] = array($name, $value);
    }
}

Here we can see that there are two ways to use it:

Example #1:

Here we only pass the header information in the $name input string, that's seperated with :

add_action( 'phpmailer_init', function( $phpmailer )
{   
    $phpmailer->AddCustomHeader( 
       'X-WU-Site: ' . parse_url( get_site_url(), PHP_URL_HOST ) 
    );  
} );

Example #2:

Here are both $name and $value non-empty:

add_action( 'phpmailer_init', function( $phpmailer )
{   
    $phpmailer->AddCustomHeader( 
       'X-WU-Site', parse_url( get_site_url(), PHP_URL_HOST ) 
    );  
} );
2
  • Nice to know this answer :) But I am curious to know what effect it will effect if someone set it $args['headers'] = 'key: value' via WP filters ?
    – Sumit
    Mar 25, 2016 at 17:16
  • The phpmailer_init hook fires later than when the wp_mail filter is applied and the custom headers from that filter are also added through the AddCustomHeader() method, as far as I understand it. @sumit
    – birgire
    Mar 25, 2016 at 17:29
3

PHP headers are strings. You can not parse them as array. You need to add you additional header as string with \r\n to make sure it is added in next line.

Example:

add_filter('wp_mail', 'ws_add_site_header', 99);
function ws_add_site_header($args) {
    $args['headers'] .= !empty($args['headers']) ? "\r\n" : '';
    $args['headers'] .= 'X-WU-Site: ' . parse_url(get_site_url(), PHP_URL_HOST);
    return $args;
}

Also please note add you additional headers at last with priority 99 so no other plugin can replace it if it is not checking that headers already present. If needed make it 999.

4
  • 1
    I dug through the source of wp_mail() and it's apparently supposed to be able to accept headers as either arrays, or as big chunks of text, and maybe sometimes the two mixed together. Wouldn't advise doing that generally but it's always an option. Mar 25, 2016 at 21:31
  • @DavidE.Smith is correct. wp_mail accepts an array for the header argument and WP will take care of adding \r\n
    – Jan Beck
    Mar 30, 2016 at 5:20
  • Please note I've mentioned PHP headers not what wp_mail accept. At last headers are strings. What does wp_mail accept was not the question! @JanBeck
    – Sumit
    Mar 30, 2016 at 5:32
  • 1
    Yes, that was the question. Why do you bring up PHP headers?
    – Jan Beck
    Mar 30, 2016 at 6:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.