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 🙂 |