Independent Editorial DeskWordPress Releases, Builds, and Operations
Back to Archive
Hardening NotesImplementation Notes

WordPress wp_mail Example: Send Transactional Email Safely

Use wp_mail safely for transactional email with validation, HTML content rules, failure logging, duplicate-send prevention, and deliverability checks.

Published

April 20, 2026

Reading Time

3 min read

Updated

April 20, 2026

Abstract transactional email delivery flow with envelope, arrow, and delivery confirmation mark.
Control LedgerHardening Notes

Hardening Notes

Baselines, access reduction, and default settings that stand up in production.

Best For

Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.

Primary Topics

Hardening NotesImplementation Notes

Editorial Focus

Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 20, 2026.

Full Report

Last reviewed: April 20, 2026

Email looks simple in WordPress until a production plugin depends on it. Password resets, order confirmations, security alerts, form notifications, and editorial workflow messages all need predictable delivery. The mistake is treating wp_mail() as proof that the recipient received the message. It is not.

This guide shows a practical wp_mail() example for sending transactional email safely from a plugin. It covers input validation, headers, HTML content, logging failures, and the operational limits of WordPress email delivery.

Who this guide is for

This article is for WordPress plugin developers, WooCommerce-adjacent implementers, agencies, and site operators who need email features that behave reliably in production.

What wp_mail() can and cannot promise

wp_mail() returns whether WordPress and PHPMailer were able to process the send attempt. A true return value does not guarantee inbox delivery. DNS configuration, SPF, DKIM, DMARC, SMTP provider reputation, recipient filtering, and bounce handling all happen outside the function.

Use wp_mail() as the application send interface, but treat deliverability as an operational system.

Basic transactional email example

<?php
function vulnwp_send_security_alert_email( $recipient_email, $post_id ) {
	$recipient_email = sanitize_email( $recipient_email );
	$post_id         = absint( $post_id );

	if ( ! is_email( $recipient_email ) || ! $post_id ) {
		return new WP_Error( 'vulnwp_invalid_email_request', 'Invalid email request.' );
	}

	$post = get_post( $post_id );

	if ( ! $post ) {
		return new WP_Error( 'vulnwp_missing_post', 'Post not found.' );
	}

	$subject = sprintf(
		'Security review needed: %s',
		wp_strip_all_tags( get_the_title( $post ) )
	);

	$message = sprintf(
		'The post "%1$s" needs a security review. Edit it here: %2$s',
		wp_strip_all_tags( get_the_title( $post ) ),
		esc_url_raw( get_edit_post_link( $post_id, '' ) )
	);

	$headers = array(
		'Content-Type: text/plain; charset=UTF-8',
	);

	$sent = wp_mail( $recipient_email, $subject, $message, $headers );

	if ( ! $sent ) {
		return new WP_Error( 'vulnwp_mail_failed', 'WordPress could not process the email send.' );
	}

	return true;
}

The example validates the recipient, checks that the post exists, strips HTML from the subject, and sends a plain text message. Plain text is often the safest default for operational alerts.

HTML email example

If the message needs links or formatting, use an explicit content type header and escape dynamic values before composing the HTML.

$subject = 'Deployment checklist ready';

$message = sprintf(
	'<p>%s</p><p><a href="%s">%s</a></p>',
	esc_html__( 'A new deployment checklist is ready for review.', 'vulnwp-mail' ),
	esc_url( admin_url( 'edit.php' ) ),
	esc_html__( 'Open WordPress admin', 'vulnwp-mail' )
);

$headers = array(
	'Content-Type: text/html; charset=UTF-8',
);

wp_mail( '[email protected]', $subject, $message, $headers );

Do not build HTML email from unsanitized form values. Email clients are inconsistent, and unsafe HTML can still create trust and phishing problems even if it does not execute JavaScript.

Log send failures without exposing secrets

Use the wp_mail_failed hook to capture failures. Avoid logging full message bodies if they may contain personal data, tokens, reset URLs, or private content.

add_action(
	'wp_mail_failed',
	function ( WP_Error $error ) {
		error_log( 'VulnWP mail failed: ' . $error->get_error_message() );
	}
);

From address and SMTP notes

Production sites should send from a domain they control and authenticate that domain with the mail provider. Many hosts block or restrict raw PHP mail. For reliable delivery, use a real SMTP or transactional email provider and configure DNS records correctly.

If changing sender details globally, use WordPress mail filters carefully and test core emails after the change. A broken global mail configuration can affect password resets, admin notifications, ecommerce emails, and plugin alerts.

Rate limiting and duplicate sends

Transactional email should not be triggered repeatedly by refreshes, retries, or repeated webhook delivery. If an email belongs to a specific event, store an event marker before or immediately after sending so the same message is not sent many times.

$already_sent = get_post_meta( $post_id, 'vulnwp_security_alert_sent', true );

if ( $already_sent ) {
	return true;
}

$result = vulnwp_send_security_alert_email( $recipient_email, $post_id );

if ( true === $result ) {
	update_post_meta( $post_id, 'vulnwp_security_alert_sent', current_time( 'mysql', true ) );
}

This is especially important for scheduled tasks, import tools, status transitions, and payment-related workflows. Duplicate email is a trust problem even when the message content is correct.

Testing workflow

  1. Test on staging with the same SMTP provider or a mail capture tool.
  2. Verify plain text and HTML rendering in at least two email clients.
  3. Test invalid recipient addresses and missing content.
  4. Trigger a failure and confirm wp_mail_failed logging works.
  5. Confirm password reset emails still work after mail configuration changes.

Production checklist

  • Validate recipient addresses with sanitize_email() and is_email().
  • Keep subjects plain and strip unexpected HTML.
  • Escape dynamic values before composing HTML email.
  • Use a real mail provider for production delivery.
  • Monitor wp_mail_failed without logging secrets.
  • Test password reset and admin emails after SMTP changes.
  • Document which plugin actions trigger email.

Common mistakes

  • Assuming true means delivered. It only means WordPress processed the send attempt.
  • Sending user input directly. Validate and escape values before including them in subject or body.
  • Forgetting content type. HTML messages need an HTML content type.
  • Logging complete messages. Email bodies can contain private data.
  • No deliverability testing. DNS and provider configuration matter as much as code.

Related reading

Email features should be reviewed with the same discipline as other plugin surfaces. Pair this with our HTTP API example for external integrations and the debug log exposure checklist.

References and further reading

Popular Guides

Popular WordPress guides to read next.

These articles connect recurring production concerns: implementation details, updates, troubleshooting, recovery paths, and operational cleanup.

Continue Reading

More from the archive.

Diagnostic dashboard scene representing a WordPress Site Health review before major updates.
01Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Site Health Check Before Major Updates: What to Review First

A pre-update WordPress Site Health checklist covering loopbacks, connectivity, debug settings, and environment readiness.

Structured data and route review scene representing permalink validation after a WordPress migration.
02Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Permalink Checklist After Migration: Catch URL Problems Early

A post-migration WordPress permalink checklist for checking rewrite rules, post URLs, archives, and redirect noise.

Technical media workspace representing image preparation and optimization before upload to WordPress.
03Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Image Optimization Checklist: What to Fix Before Upload

A practical WordPress image optimization checklist covering dimensions, compression, formats, and Media settings before upload.