WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question

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 ) 
    );  
} );
share|improve this answer
    
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 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 at 17:29
    
Yes agree and true, Thanks! – Sumit Mar 25 at 17:31
up vote 5 down vote accepted

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.

share|improve this answer

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.

share|improve this answer
    
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. – David E. Smith Mar 25 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 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 at 5:32
    
Yes, that was the question. Why do you bring up PHP headers? – Jan Beck Mar 30 at 6:11

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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