WordPress Emails

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 where example.com is the home_url of the primary site network_home_url(), unless customized via the wp_mail_from filter.
  • “From” name: WordPress unless customized via the wp_mail_from_name filter.

It uses the PHPMailer library for sending email behind the scenes (included with WordPress), which configures the following additional headers:

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.

Diagram of how server IP, Return-Path and From headers are used for DKIM and SFP aligment

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


Discussion

Leave a Reply

Your email address will not be published. Required fields are marked *