
This quick tutorial will show you how to send custom emails to your registered users using the wp_mail() function.
- Create your own email templates.
- And learn how to overwrite the system user emails when you have new user registration, password change or email confirmation after a successful registration.
So, let’s not waste any more time and dive right into it.
See here a custom new registration email I have created for one of my sites using the approach below.
The Template
First, you need to have some generic HTML templates for your emails.
Not going into much detail here, but there are a couple of tips while you code your HTML template.
- Use XHTML 1.0 transitional doctype with tables and inline styles, this way you will be sure that your email template looks good on different email clients.
- Load your images from a CDN, not your server.
- If you load a font from Google font, as I do in this tutorial, limit the font to only one with no more 2 styles (e.g normal and bold).
Note: You can have as many different email templates. For simplicity for this tutorial, I decided to go with one generic template for all of our emails. It is still flexible, but you have to create/load multiple templates if you need more.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title><?php echo $email_custom_data['title'];?> - Company Name</title> | |
<style> | |
@font-face { | |
font-family: 'Roboto'; | |
font-style: normal; | |
font-weight: 400; | |
src: local('Roboto'), local('Roboto-Regular'), url('//fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2') format('woff2'); | |
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; | |
} | |
</style> | |
</head> | |
<body> | |
<div width="100%" bgcolor="#fbfbfb" style="background-color:#fbfbfb;font-family:'Roboto',sans-serif;font-size:14px;color:#666666;margin:0"> | |
<div style="max-width:600px;margin:auto"> | |
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width:600px"> | |
<tbody> | |
<!-- Header --> | |
<tr> | |
<td> | |
<table border="0" cellpadding="0" cellspacing="0" width="100%"> | |
<tbody> | |
<!-- Logo --> | |
<tr> | |
<td align="center" style="padding-top:40px;padding-bottom:40px"> | |
<a href="<?php echo esc_url(home_url('/'));?>"> | |
<img src="<?php echo $email_custom_data['logo_url'];?>" alt="" width="75" /> | |
<span style="display:none;">Company Name</span> | |
</a> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</td> | |
</tr> | |
<!-- Body --> | |
<tr> | |
<td style="padding-bottom:32px"> | |
<table style="border-radius:4px;background:#ffffff;border:solid 1px #ffffff;padding:24px 0" cellpadding="0" cellspacing="0" width="100%"> | |
<tbody> | |
<!-- Featured image --> | |
<tr> | |
<td style="padding-bottom:14px"> | |
<div style="width:100%;max-width:100%;text-align:center"> | |
<img src="<?php echo $email_custom_data['featured_image_url'];?>" alt="" width="480" /> | |
</div> | |
</td> | |
</tr> | |
<!-- Title --> | |
<tr> | |
<td style="font-weight:600;color:#666666;font-size:24px;text-align:center;letter-spacing:-0.4px;line-height:34px;padding:0 0 16px"> | |
<?php echo $email_custom_data['title'];?> | |
</td> | |
</tr> | |
<!-- Short content summary --> | |
<tr> | |
<td style="color:#666666;font-size:16px;line-height:1.8em;text-align:center"> | |
<div style="max-width:408px;width:100%;margin:0 auto"> | |
<?php echo $email_custom_data['content'];?> | |
</div> | |
</td> | |
</tr> | |
<!-- CTA button --> | |
<tr> | |
<td style="text-align:center;padding:24px 0 16px"> | |
<a href="<?php echo $email_custom_data['cta_button_url']; ?>" style="display:inline-block;padding:14px 20px 14px 20px;border-radius:4px;text-align:center;text-decoration:none;color:#ffffff;font-size:16px;background:#0033dd" target="_blank"> | |
<?php echo $email_custom_data['cta_button_text'];?> | |
</a> | |
</td> | |
</tr> | |
<!-- Aadditional footer content --> | |
<tr> | |
<td style="text-align:center;padding-top:20px;padding-bottom:20px;"> | |
<div style="font-weight:600;color:#666666;font-size:24px;text-align:center;letter-spacing:-0.4px;line-height:34px;padding:0 0 16px"> | |
NOT ready to get started? | |
</div> | |
<div style="font-size:14px;line-height:1.6;"> | |
Check out our free <a href="<?php echo esc_url(home_url('/tools'));?>" target="_blank" style="color:#0033dd">tools</a>. | |
</div> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</td> | |
</tr> | |
<!-- Footer --> | |
<tr> | |
<td style="text-align:center;padding:20px 60px"> | |
<div style="font-size:12px;line-height:1.4;color:#cccccc"> | |
Thanks for reading! If you don't want to receive emails from our system you can | |
<a href="<?php echo esc_url(home_url('/login'));?>" target="_blank" style="color:#0033dd">unsubscribe</a> by updating your settings under the profile page. | |
</div> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</body> | |
</html> |
The Code
Once we have our email template, we need to create a couple of functions to handle our emails.
The first function would be used to send an email within your system. You can specify an email address if you want to send an email to non-registered users. By default, this function will look for a WordPress user.
The second function would overwrite existing email functionality like new user registration or password reset emails.
Note: I tried to keep these functions as simple as possible so you can extend and customize them to meet your needs. However, the same approach could be applied to many different scenarios.
<?php | |
/** | |
$email_custom_data = array( | |
'title' => '', | |
'content' => '', | |
'logo_url' => '', | |
'featured_image_url' => '', | |
'cta_button_url' => '', | |
'cta_button_text' => '', | |
) | |
*/ | |
function compose_email(array $email_custom_data, string $email_address = '') { | |
// If we don't have an email passed then the user must be logged in. | |
if (!$email_address) { | |
$user = get_current_user_id(); | |
if (!$user) { | |
return false; | |
} | |
$curr_user = get_userdata(get_current_user_id()); | |
$email_address = $curr_user->data->user_email; | |
} | |
foreach ($email_custom_data as $key => $value) { | |
$email_custom_data[$key] = html_entity_decode($value); | |
} | |
// Generate our email template and apply all the data from $email_custom_data. | |
ob_start(); | |
include_once 'send_email_template.php'; | |
$email_to = $email_address; | |
$email_subject = $email_custom_data['title']; | |
$email_headers = ['From: Company Name <donotrespond@companyname.com>', 'Cc: support@companyname.com']; | |
$email_body = ob_get_contents(); | |
ob_get_clean(); | |
// Send an email using the wp_mail() function. | |
if (!wp_mail($email_to, $email_subject, $email_body, $email_headers)) { | |
return false; | |
} | |
return true; | |
} | |
function overwrite_email(array $email_data, array $email_custom_data) { | |
foreach ($email_custom_data as $key => $value) { | |
$email_custom_data[$key] = html_entity_decode($value); | |
} | |
// Generate our email template and apply all the data from $email_custom_data. | |
ob_start(); | |
include_once 'send_email_template.php'; | |
$email_data['subject'] = $email_custom_data['title']; | |
$email_data['headers'] = ['From: Company Name <donotrespond@companyname.com>', 'Cc: support@companyname.com']; | |
$email_data['message'] = ob_get_contents(); | |
ob_get_clean(); | |
return $email_data; | |
} |
The Actions
We are all set up and ready to use our newly created custom email feature. To do that, we need to create a couple of action hooks.



The first one would use our compose_email() function and will overwrite an email when a registered user requests a password reset within our system. And the second action will overwrite the email when we have a new user registration.
Both actions are very similar. The only difference is that we need to create and return the new email contents when we overwrite the existing WordPress functionality for new user registration.
Note: We also need to specify the content type for our emails, text/html. By default, WordPress is set to send text-only emails.
<?php | |
add_action('wp_mail_content_type', function() { | |
return "text/html"; | |
}, 10, 1); | |
add_action('retrieve_password_message', function($message, $reset_key, $user_login, $user_data) { | |
$logo_url = esc_url('https://cdn-domain-name.com/assets/img/logo.png'); | |
$featured_url = esc_url('https://cdn-domain-name.com/assets/img/featured-image.png'); | |
$reset_url = esc_url(home_url('/wp-login.php?action=rp&key=') . $reset_key . '&login=' . $user_login); | |
compose_email( | |
array( | |
'title' => __('Password reset requested.', 'textdomain'), | |
'content' => html_entity_decode(sprintf(esc_html__('<p>%1$s,<strong> ' . $user_data->display_name . '</strong></p><p>%2$s</p><p>%3$s<br />'. $reset_url . '</p>'), | |
__('Hi', 'textdomain'), // 1 | |
__('You have requested a password reset! If this was a mistake, just ignore this email and nothing will happen.', 'textdomain'), // 2 | |
__('To reset your password please click on the button bellow or copy & paste the link to enter your new password', 'textdomain') // 3 | |
)), | |
'logo_url' => $logo_url, | |
'featured_image_url' => $featured_url, | |
'cta_button_url' => $reset_url, | |
'cta_button_text' => __('Reset your password', 'textdomain') | |
), | |
$user_data->user_email | |
); | |
}, 10, 4); | |
add_action('wp_new_user_notification_email', function($email_data, $user) { | |
$reset_key = get_password_reset_key($user); | |
$logo_url = esc_url('https://cdn-domain-name.com/assets/img/logo.png'); | |
$featured_url = esc_url('https://cdn-domain-name.com/assets/img/featured-image.png'); | |
$reset_url = esc_url(home_url('/wp-login.php?action=rp&key=') . $reset_key . '&login=' . $user->data->user_login); | |
return overwrite_email( | |
$email_data, | |
array( | |
'title' => __('Welcome to Company Name!', 'textdomain'), | |
'content' => html_entity_decode(sprintf(esc_html__('<p>%1$s,<strong> ' . $user->data->display_name . '</strong>%2$s</p><p>%3$s</p><p>%4$s<br />' . $reset_url . '</p>'), | |
__('Hi', 'textdomain'), // 1 | |
__('and thank you for joining us!', 'textdomain'), // 2 | |
__('You have successfully registered an account.', 'textdomain'), // 3 | |
__('Click on the button bellow or copy & paste the link to veriy your email address and enter your new password.', 'textdomain') // 4 | |
)), | |
'logo_url' => $logo_url, | |
'featured_image_url' => $featured_url, | |
'cta_button_url' => $reset_url, | |
'cta_button_text' => __('Reset your password', 'textdomain') | |
) | |
); | |
}, 10, 2); |
Bonus: In some cases, you may want to disable system emails sent on password change to avoid duplicates.
add_filter('send_password_change_email', '__return_false', 10, 1); // users add_filter('send_email_change_email', '__return_false', 10, 1); // admin
What’s Next?
You can use the above approach to send custom emails anywhere within your system. I didn’t have an example in this tutorial, but you can use the compose_email() function without relying on any existing WordPress functionality or having a registered user.
‘Til the next time.