All transactional emails in WordPress are sent by the wp_mail()
function which is one of the “pluggable” functions (can be overwritten) in wp-includes/pluggable.php
.
It sets the following email headers by default:
- “From” email address:
wordpress@example.com
whereexample.com
is thehome_url
of the primary sitenetwork_home_url()
, unless customized via thewp_mail_from
filter. - “From” name:
WordPress
unless customized via thewp_mail_from_name
filter.
It uses the PHPMailer
library for sending email behind the scenes (included with WordPress), which configures the following additional headers:
- SMTP envelope
Sender
orMAIL FROM
in SMTP (which gets turned intoReturn-Path
by the recipient server) toini_get('sendmail_from')
, if set; or dynamically resolved by the mail transfer agent.
This address is used for bounces or notifications about any delivery issues. Importantly, it is also used for SFP and DMARC alignment — the hostname (domain) of Return-Path
should match the From
domain.

On many hosts the Sender
is set to something like www-data@example.com
which is (1) an invalid/inactive address which can’t even process the bounce emails, (2) will fail the SFP alignment checks by Gmail and other email providers.
Use the phpmailer_init
filter to set the Sender
email address to the From
address:
add_action(
'phpmailer_init',
function ( $phpmailer ) {
$phpmailer->Sender = $phpmailer->From;
}
);
Tip: Use the Mail Pilot plugin to configure the From
email address and name, and ensure that Return-Path
email matches the From
email address for SFP and DMARC alignment. It also supports DKIM signing for extra reliable delivery.
Transactional Email Types
Password Reset
The password reset links in the footer of the login form wp-login.php
point to the main site of the network in wp_lostpassword_url()
instead of the current site:
function wp_lostpassword_url( $redirect = '' ) {
$args = array(
'action' => 'lostpassword',
);
if ( ! empty( $redirect ) ) {
$args['redirect_to'] = urlencode( $redirect );
}
if ( is_multisite() ) {
$blog_details = get_site();
$wp_login_path = $blog_details->path . 'wp-login.php';
} else {
$wp_login_path = 'wp-login.php';
}
$lostpassword_url = add_query_arg( $args, network_site_url( $wp_login_path, 'login' ) );
// ...
}
Same with the password reset form, which sends a POST request to the main site of the network:
<form name="lostpasswordform" id="lostpasswordform" action="<?php echo esc_url( network_site_url( 'wp-login.php?action=lostpassword', 'login_post' ) ); ?>" method="post">
...
</form>
Technically, WordPress supports all password reset functionality at the sub-site level. Use the following filters to adjust the URLs to point to the current sub-site to align the password reset experience with the branding of each site:
// Allow password reset from the specific blog.
function wpelevator_password_reset_local( $url ) {
// Specify `/` because both methods have different defaults.
return str_replace( network_site_url( '/' ), site_url( '/' ), $url );
}
add_filter( 'retrieve_password_message', 'wpelevator_password_reset_local' );
add_filter( 'lostpassword_url', 'wpelevator_password_reset_local' );
// Adjust the form action URL for the password reset form.
add_filter(
'network_site_url',
function ( $url, $path, $scheme ) {
if ( 'login_post' === $scheme ) {
return wpelevator_password_reset_local( $url );
}
return $url;
},
10,
3
);
Resources
- wp_mail() is NOT broken by Konstantin.
Leave a Reply