Sending emails is a core part of the WordPress experience, whether it is for user registrations, password resets, or custom notifications. However, many developers feel frustrated when they try to send a beautiful, branded email only to find that WordPress has stripped all the tags and delivered it as boring, plain text.

By default, the WordPress wp_mail() function sends emails using the text/plain content type. This means any HTML you include in your message body will be displayed literally to the recipient. In this guide, you will learn the three best ways to send HTML formatted emails in WordPress, ranging from quick header fixes to professional-grade templating techniques.

Understanding Why wp_mail() Defaults to Plain Text

WordPress uses the PHPMailer library under the hood to handle email delivery. To maintain the highest level of compatibility across various server environments and email clients, the default MIME type is set to text/plain. While this is safe, it limits your ability to use bold text, images, or custom layouts.

To overcome this, you must explicitly tell WordPress to change the Content-Type header to text/html. There are several ways to achieve this, depending on whether you want a global change or a one-off modification.

Method 1: Using the wp_mail_content_type Filter

The most common solution found in the developer community is using a filter hook. This approach tells WordPress to change the default content type for all emails processed through the system.

You can add the following snippet to your theme's functions.php file or a custom plugin:

// In theme's functions.php or plug-in code:

function wpse27856_set_content_type(){
    return "text/html";
}
add_filter( 'wp_mail_content_type','wpse27856_set_content_type' );

The Risk of Global Filters

Once you add this filter, every single email sent by your WordPress site—including core emails and those from other plugins—will be sent as text/html. If a plugin expects to send plain text, the formatting might break or look strange.

To mitigate this, it is a best practice to remove the filter immediately after your custom email is sent:

// Set the filter
add_filter( 'wp_mail_content_type', 'wpse27856_set_content_type' );

// Send the mail
wp_mail( $to, $subject, $body );

// Remove the filter to avoid affecting other emails
remove_filter( 'wp_mail_content_type', 'wpse27856_set_content_type' );

If you want to avoid the risks of global filters, the cleanest approach is to pass the Content-Type directly into the wp_mail() function call. This localizes the change only to that specific email.

Here is how you can implement this:

$to = '[email protected]';
$subject = 'The subject';
$body = '<h1>Welcome!</h1><p>This is an <strong>HTML</strong> email.</p>';
$headers = array('Content-Type: text/html; charset=UTF-8');

wp_mail( $to, $subject, $body, $headers );

Using PHP Heredoc for Cleaner Code

When dealing with large blocks of HTML, concatenating strings with quotes can become messy and error-prone. Using PHP's Heredoc syntax allows you to write clean HTML within your PHP scripts:

$email_to = '[email protected]';
$email_subject = 'Email subject';

// <<<EOD starts the heredoc string
$email_body = <<<EOD
<html>
    <body>
        <h1 style="color: #333;">Hello!</h1>
        <p>This is your new <b style="color: red;">password</b>: {$password}</p>
    </body>
</html>
EOD;

$headers = ['Content-Type: text/html; charset=UTF-8'];

$send_mail = wp_mail( $email_to, $email_subject, $email_body, $headers );

Method 3: Advanced Templating with ob_start()

For complex emails, writing HTML directly inside a PHP variable is difficult to maintain. A more professional approach is to create a separate PHP file for your email template and load it using output buffering (ob_start).

This method allows you to use standard WordPress functions like get_bloginfo() or get_stylesheet_directory_uri() inside your email template.

Step 1: Create your template file (email-template.php)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
    <div style="padding: 20px; background: #f4f4f4;">
        <h2>Notification from <?php echo get_bloginfo('name'); ?></h2>
        <p>Hello, a new update is available for your account.</p>
    </div>
</body>
</html>

Step 2: Load and send the template

$to = '[email protected]';
$subject = 'Your Custom Notification';

ob_start();
// Include the template file from your theme folder
include(get_stylesheet_directory() . '/email-template.php');
$body = ob_get_contents();
ob_end_clean();

$headers = array('Content-Type: text/html; charset=UTF-8', 'From: Your Site <[email protected]>');
wp_mail( $to, $subject, $body, $headers );

Best Practices and Common Pitfalls

When sending HTML emails in WordPress, keep these professional tips in mind:

  1. Always Inline Your CSS: Most email clients (like Outlook or Gmail) will ignore <style> tags in the head or external stylesheets. You must use inline style="" attributes on your HTML elements.
  2. Include a Charset: Always specify charset=UTF-8 in your headers to ensure special characters and emojis display correctly.
  3. Check Your "From" Header: By default, WordPress sends emails from [email protected]. Adding a custom From: header makes your emails look more professional and improves deliverability.
  4. Use an SMTP Plugin: Even with perfect HTML, your emails might end up in spam. Use a plugin like WP Mail SMTP to route your emails through a dedicated service like SendGrid, Mailgun, or Amazon SES.

Frequently Asked Questions

Can I send attachments with HTML emails?

Yes! The wp_mail() function accepts a 5th parameter for attachments. You can pass a single string path or an array of file paths to the local server files.

Why does my HTML code show up as text in the inbox?

This usually happens because the Content-Type header was not correctly set to text/html. Double-check that your headers array is correctly formatted and passed as the 4th argument to wp_mail().

Is it safe to use the global filter?

It is safe only if you remove it immediately after your call. If you leave add_filter('wp_mail_content_type', ...) active in your functions.php, it will likely break the formatting of other plugins that rely on plain text emails.

Wrapping Up

Sending HTML emails with wp_mail() is straightforward once you understand how headers and filters work. For quick notifications, passing the header directly in the function call is the most efficient and safest method. For larger projects, using a dedicated template file with output buffering will keep your code clean and manageable.

Always remember to test your emails across different clients to ensure your HTML and inline CSS render as expected!