How to send WordPress default emails as HTML
WordPress sends its own emails as plain text. Why, which 25 emails that means, why the one-line content-type fix breaks them, and how to give them a layout.
Open the password reset email WordPress sends and it looks like something from 2005: a wall of plain text, a raw URL, no site name at the top, no link home. The same goes for the new-user welcome, the “your site has updated” report and the comment notification. Most other email in your users’ inboxes, from the bank to the newsletter, has a layout. WordPress’s own have none.
This guide covers why those emails are plain text, exactly which emails are involved, why the popular one-line fix does more harm than good, and how to give them a simple HTML layout without touching the emails from WooCommerce and your other plugins.
Why WordPress emails are plain text
wp_mail(), the function every WordPress email goes through, sends text/plain unless a caller says otherwise. Plugins that care about how their email looks, such as WooCommerce, set the content type to HTML and build a template. WordPress core does not. It writes each of its emails as a string with line breaks in it and hands it over as is.
Plain text is a safe default for a site whose theme, language and mail setup core knows nothing about. It just means the emails core writes are the plainest ones your site sends.
Which emails this means
Across WordPress core there are 25 such emails: 19 on a single site, and 6 more that exist only on a multisite network.
- Accounts (9): the password reset link; the new-account email to the user and the notice to the admin; password changed, to the user and to the admin; account email changed; the confirmation for a new account email; the confirmation for a new site admin email; and the notice to the old admin address after it changes.
- Comments (2): the new comment notification to the post author, and the comment awaiting moderation email.
- Updates and site health (4): WordPress updated automatically; plugins or themes updated automatically; the automatic update debug report; and the technical issue (recovery mode) email.
- Privacy (4): the confirmation for a personal data export or erasure request; the notice that a request was confirmed; the export ready email; and the erasure fulfilled email.
- Multisite only (6): activate a new account; activate a new site; the new account and new site welcome emails; and the confirmation and notice for a network admin email change.
If a plugin sends it, it is not on this list. Form notifications, order emails and newsletters are that plugin’s business, and each has its own template settings.
The fix that breaks things
Search this problem and the first answer is a snippet:
add_filter( 'wp_mail_content_type', function () {
return 'text/html';
} );
It does make WordPress declare its emails as HTML. It also does three things you did not ask for.
- Line breaks disappear. HTML ignores newlines, so a plain-text email declared as HTML arrives as one long paragraph, with the reset link jammed against the sentence before it.
- It applies to every email on the site. Any plugin that sends plain text and trusts the default content type now sends “HTML” with no markup in it, and its line breaks disappear too.
- Nothing gets a layout. The email is still the same string; it is just labelled differently.
The snippet changes the label without changing the email. What you actually want is a template around core’s emails, applied only to core’s emails, with the text inside left as core wrote it.
What a good fix looks like
Three rules keep this safe:
- Only wrap the emails WordPress core writes. Identify each one as core builds it, not by guessing from a subject line that is translated and filterable.
- Leave every other email alone. Anything from a plugin, and anything already sent as HTML, goes out untouched.
- Keep the text. Escape it, keep the line breaks, turn the web addresses into links so the reset link still works, and put a header and footer around it. Do not rewrite what core said.
A layout built for mail clients also means no images, no web fonts and every style inline, since many clients block remote images, ignore web fonts, and strip or limit stylesheets.
Set it up with Adminkeep
Adminkeep is a free plugin made of small features that each have one switch. Its Nice Default Emails feature does the above and nothing more: it recognises each of the 25 core emails as WordPress builds it, and sends it with your site’s name at the top, the text in a card, and the site’s name and address at the bottom. If you don’t have Adminkeep yet, installation takes a minute.
- Go to Settings → Adminkeep and switch on Nice Default Emails in the Email group.
- That is the whole setup. There are no options: one layout, named and linked from your site’s own settings.
- Send yourself a password reset from the login page to see it.
What you get:
- The site’s name heads the email and links home. The footer names the site again with its address.
- The text is exactly what WordPress wrote. Line breaks are kept, web addresses become links, and nothing in an email is read as markup.
- Colours are WordPress admin’s own, the fonts are the system’s, there are no images. Mail clients that support dark-mode styles get a dark version.
- WooCommerce and every other plugin’s email is left as it is, and so is any email already sent as HTML.
- Nothing is stored. Switch it off and the next email goes out as plain text again.
It works with or without an SMTP setup. The layout is applied just before the email reaches the mailer, so it goes out the same way through Adminkeep’s own SMTP feature, another SMTP plugin, or PHP mail. With Email Log on, the log keeps the HTML version and previews it the way a mail client would.
The Nice Default Emails docs list every email by id, with what the layout holds and what is left alone.
See them in your own inbox
Most of these emails are awkward to trigger on purpose. You would need a new account, a comment held for moderation, an automatic update, a personal data request. Adminkeep can send each one for you, through the WordPress function that sends it on a live site, to one address:
wp adminkeep feature enable nice_emails
wp adminkeep emails list
wp adminkeep emails send password_reset [email protected]
wp adminkeep emails send --all [email protected]
On a single site that is 19 emails; the six multisite ones are listed and sent only on a network. Every email goes to the address you give and to nobody else. What an email needs is created for the run and deleted after it: a throwaway account, a private post with two comments, a personal data request. Your own accounts are not touched, and settings WordPress writes while sending are put back, so the links in those emails do nothing. The WP-CLI docs cover the flags.
Keep one email plain, or wrap one of your own
For developers, one filter decides. It is asked before each plain-text email goes out, with true for one of core’s emails and false for anything else, the email’s id, and the wp_mail() arguments:
add_filter( 'adminkeep_nice_emails_wrap', function ( $wrap, $email_id, $atts ) {
if ( 'comment_moderation' === $email_id ) {
return false; // keep this one plain
}
return $wrap;
}, 10, 3 );
Return true for a plain-text email of your own and it gets the same layout. Emails that already declare HTML are not asked about.