Contents
Introduction
This tutorial explains in full detail how to send HTML emails in WordPress using wp_mail, how to create and use HTML templates, how to handle headers, attachments, content types, and common debugging and deliverability considerations. You will find multiple complete, copy-paste-ready examples, a recommended pattern for template loading and safe placeholder replacement, how to set up SMTP via phpmailer_init for reliable delivery, and best practices to avoid common mistakes.
Quick summary
- wp_mail is the WordPress wrapper around PHPMailer. It accepts arguments: to, subject, message, headers and attachments.
- To send HTML email you can either add a Content-Type header or add a wp_mail_content_type filter that returns text/html.
- Templates are best stored in a theme/plugin folder and loaded via a function that replaces placeholders with sanitized values.
- For reliable delivery use an authenticated SMTP provider or a plugin such as WP Mail SMTP you can also configure PHPMailer directly using the phpmailer_init hook.
wp_mail basics
Signature of the function:
// wp_mail( stringarray to, string subject, string message, arraystring headers = , array attachments = array() )
Arguments:
- to: email or array of emails.
- subject: subject string.
- message: message body (can be HTML when Content-Type set to text/html).
- headers: string or array of headers (e.g., From: Name ltemail@domain.comgt or Content-Type: text/html charset=UTF-8).
- attachments: array of file system paths to files to attach.
Sending a minimal HTML email
Two valid approaches to enable HTML content:
- Pass the Content-Type header in the headers argument.
- Temporarily add a filter with add_filter(wp_mail_content_type, …) and remove it after sending.
Example: add Content-Type in headers (recommended simple approach)
Example: use wp_mail_content_type filter (cleaner when sending multiple emails)
Hello from wp_mailThis message uses the wp_mail_content_type filter.
, array( From: Example ltno-reply@example.comgt ) ) // remove the filter so other mails are not forced to HTML remove_filter( wp_mail_content_type, set_html_mail_content_type ) ?>
Templates: structure and loading
Store email templates in a dedicated folder, for example: wp-content/themes/your-theme/emails/ or inside your plugins folder. Use a loader function that extracts variables and returns the final HTML. Always ensure user-supplied values are sanitized/escaped for safety.
Template file example (emails/welcome.php)
lt!-- save as emails/welcome.php --gt
lt!doctype htmlgt
lthtml lang=engt
ltheadgtltmeta charset=utf-8gtlt/headgt
ltbody style=font-family: Arial, sans-serif color: #333 margin: 0 padding: 20pxgt
lttable width=100% cellpadding=0 cellspacing=0 role=presentationgt
lttrgtlttdgt
lth2 style=color:#2c3e50 margin-top:0gtWelcome, {{username}}!lt/h2gt
ltpgtThanks for registering. Click the button to verify your email.lt/pgt
ltpgtlta href={{verification_link}} style=display:inline-block padding:10px 18px background:#2ecc71 color:#fff text-decoration:none border-radius:4pxgtVerify Emaillt/agtlt/pgt
ltp style=font-size:12px color:#999gtIf you didnt sign up, ignore this email.lt/pgt
lt/tdgtlt/trgt
lt/tablegt
lt/bodygt
lt/htmlgt
Loader and placeholder replacement (safe and flexible)
value
@return string Rendered HTML
/
function mytheme_load_email_template( template_name, vars = array() ) {
// Look in child theme then parent theme
template_path = locate_template( emails/ . template_name . .php )
// If not found in theme, check plugin path as fallback (example)
if ( ! template_path ) {
plugin_template = plugin_dir_path( __FILE__ ) . emails/ . template_name . .php
if ( file_exists( plugin_template ) ) {
template_path = plugin_template
}
}
if ( ! template_path ) {
return
}
// Read file content (dont execute arbitrary PHP in templates unless intended)
template = file_get_contents( template_path )
// Replace placeholders like {{placeholder}} with escaped values
foreach ( vars as key => value ) {
// If value contains HTML you trust, use it cautiously. Otherwise escape:
safe = wp_kses_post( value ) // allows common HTML tags adjust as needed
template = str_replace( {{ . key . }}, safe, template )
}
return template
}
?>
Send using the template
John Doe,
verification_link => esc_url_raw( https://example.com/verify?token=abc123 )
) )
headers = array(
From: My Site ltno-reply@example.comgt,
Content-Type: text/html charset=UTF-8
)
wp_mail( john@example.com, Welcome to My Site, email_html, headers )
?>
Attachments
Attachments must be provided as absolute file paths on the server, not as URLs. If you have a media library attachment ID, use get_attached_file(attachment_id) to get the path.
Setting default From address and name
Rather than passing a From header every time, you can hook into wp_mail_from and wp_mail_from_name filters:
Configuring SMTP and PHPMailer
By default WordPress uses the PHP mail backend. For reliable delivery you should use authenticated SMTP or a transactional mail service (SendGrid, Mailgun, Amazon SES, etc.). Two approaches:
- Install and configure a plugin such as WP Mail SMTP or similar.
- Programmatically configure PHPMailer using the phpmailer_init action.
Configure PHPMailer via phpmailer_init
isSMTP()
phpmailer->Host = smtp.example.com
phpmailer->SMTPAuth = true
phpmailer->Port = 587
phpmailer->Username = smtp-username
phpmailer->Password = smtp-password
phpmailer->SMTPSecure = tls // ssl or tls as required
phpmailer->From = no-reply@example.com
phpmailer->FromName = My Site
}
?>
Styling HTML emails: best practices
- Keep layout simple many email clients have poor CSS support.
- Prefer table-based layout for complex placements because of inconsistent CSS support.
- Inline styles are the most reliable. Use a CSS inliner tool in your build process (e.g., Premailer, Juice) to convert ltstylegt blocks to inline styles.
- Avoid external CSS files images should be absolute URLs (https) hosted on your server or CDN.
- Test across clients (Gmail, Outlook, Apple Mail, mobile) and use tools like Litmus or Email on Acid for comprehensive testing, or Mailtrap/Mailhog locally.
Example of a compact, inlined HTML snippet for an email
lttable width=100% cellpadding=0 cellspacing=0 role=presentation style=background:#f6f6f6padding:20pxgt
lttrgtlttd align=centergt
lttable width=600 cellpadding=0 cellspacing=0 role=presentation style=background:#ffffffborder-radius:6pxoverflow:hiddengt
lttrgtlttd style=padding:20pxgt
lth2 style=color:#333margin:0 0 10pxgtHello {{username}}lt/h2gt
ltp style=color:#666margin:0 0 15pxgtWelcome to our service.lt/pgt
lta href={{action_link}} style=background:#0073aacolor:#fffpadding:10px 16pxtext-decoration:noneborder-radius:4pxdisplay:inline-blockgtGet startedlt/agt
lt/tdgtlt/trgt
lt/tablegt
lt/tdgtlt/trgt
lt/tablegt
Security and data handling
- Sanitize recipient email addresses with sanitize_email().
- Escape or sanitize placeholder data before inserting into templates: esc_html() for plain text, esc_url() for links, and wp_kses_post() for safe HTML fragments.
- Never insert raw user-supplied HTML into templates without rigorous sanitization.
- Use nonces and capability checks on any actions that trigger emails from front-end forms.
Debugging delivery problems
- Enable WP_DEBUG and check PHP error logs for fatal errors.
- Inspect the return value of wp_mail. It returns true on success and false on failure (but success does not guarantee delivery).
- Hook into phpmailer_init to inspect PHPMailer internals dump errors from phpmailer->ErrorInfo if sending fails.
- Use Mailtrap or MailHog on dev machines to capture and review outbound messages without sending them to real addresses.
- Check SPF, DKIM, and DMARC for your sending domain to avoid spam filtering.
Example: log failures and PHPMailer error info
Scaling and performance considerations
- wp_mail is fine for transactional emails (password reset, notifications), but avoid using it for large bulk sends in a single request (time-outs, throttling). Use background queueing (WP Cron, action scheduler, or a queue worker) for bulk operations.
- Use transactional email providers (Mailgun, SendGrid, Amazon SES) for high deliverability and analytics.
- Consider batching and rate limits to avoid being blacklisted by recipients or SMTP providers.
Complete real-world example: send a templated HTML welcome email with attachment and SMTP configured
Below is an integrated example: template loader, set content type via headers, attach a file from the uploads folder, and configure PHPMailer via phpmailer_init for SMTP. Copy the parts you need into your plugin or theme (functions.php).
isSMTP()
phpmailer->Host = smtp.example.com
phpmailer->SMTPAuth = true
phpmailer->Port = 587
phpmailer->Username = smtp_user
phpmailer->Password = smtp_pass
phpmailer->SMTPSecure = tls
phpmailer->From = no-reply@example.com
phpmailer->FromName = My Site
}
// 2) Template loader (as shown earlier)
function mytheme_load_email_template( template_name, vars = array() ) {
template_path = locate_template( emails/ . template_name . .php )
if ( ! template_path ) {
template_path = plugin_dir_path( __FILE__ ) . emails/ . template_name . .php
}
if ( ! template_path ! file_exists( template_path ) ) {
return
}
template = file_get_contents( template_path )
foreach ( vars as key => value ) {
safe = wp_kses_post( value )
template = str_replace( {{ . key . }}, safe, template )
}
return template
}
// 3) Actual send
function my_send_welcome_email( user_email, username ) {
to = sanitize_email( user_email )
if ( ! is_email( to ) ) {
return false
}
message = mytheme_load_email_template( welcome, array(
username => username,
verification_link => esc_url_raw( https://example.com/verify?token= . wp_hash( user_email ) )
) )
headers = array(
From: My Site ltno-reply@example.comgt,
Content-Type: text/html charset=UTF-8
)
// Attach a welcome PDF from uploads if it exists
upload_dir = wp_upload_dir()
attachment_file = trailingslashit( upload_dir[basedir] ) . welcome-guide.pdf
attachments = array()
if ( file_exists( attachment_file ) ) {
attachments[] = attachment_file
}
return wp_mail( to, Welcome to My Site, message, headers, attachments )
}
?>
Resources and references
- WordPress developer reference: wp_mail
- Best practices for sending emails from WordPress
- Use Mailtrap, MailHog, or other testing tools when developing locally.
Final checklist before going live
- Confirm Content-Type is set to text/html when sending HTML.
- Sanitize recipient addresses and user-supplied placeholders.
- Use inline CSS or an inliner tool for styles.
- Verify attachments use server file paths and exist.
- Configure SMTP or a transactional mail provider for production deliverability.
- Test on multiple clients and using an email testing tool.
- Monitor SPF/DKIM/DMARC and set them up for your sending domain.
|
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |
