Transactional email is defined by its trigger: one user action produces one message to one recipient about that action. A password reset request generates a reset link; a completed order generates a confirmation. The message exists because the user did something, and it goes only to that user.
That trigger-based definition is what separates transactional from marketing email – not the content, not the formatting, not whether it contains links. Marketing email (newsletters, promotions, announcements) goes to a list, governed by consent rules and unsubscribe requirements. Transactional email goes to one person who initiated the exchange, and most jurisdictions give it a legal carve-out as a result.
WordPress sends transactional email for a fixed set of core events. WooCommerce adds a larger set for the order and customer lifecycle. Every plugin in the ecosystem can add more. The default sending path – wp_mail() routing through PHP’s mail() function to the server’s local mail transfer agent – fails silently on most modern managed hosting. It is a structural failure of the default stack, not a configuration problem.
What WordPress core sends
WordPress 7.0 generates transactional email from four source locations in core. The sending function in every case is wp_mail(), defined in wp-includes/pluggable.php.
New user registration. Triggered by wp_new_user_notification() in wp-includes/pluggable.php. Generates two messages: one to the new user with their login link (or password if the option is set), and one to the site administrator notifying them of the registration. Both are sent in one function call with separate recipients.
Password reset. Triggered by retrieve_password() in wp-includes/user.php. Sends one message to the email address associated with the account, containing the reset link. The link is time-limited and single-use.
New comment. Triggered by wp_notify_postauthor() and wp_notify_moderator() in wp-includes/pluggable.php. Generates one message to the post author (when the setting is on) and one to the administrator if the comment requires moderation.
Automatic update notifications. Triggered by WP_Automatic_Updater::send_plugin_theme_email() in wp-admin/includes/class-wp-automatic-updater.php. Sends one message to the administrator reporting what was updated, what failed, and whether action is required.
That is the complete set. WordPress core does not send order confirmations, cart notifications, welcome sequences, or anything membership-related without a plugin. The core email footprint is small: registration, authentication, commenting, and update monitoring, all routed through wp_mail(), all overridable via the wp_mail filter and the PHPMailer class that wp_mail() delegates to.
What WooCommerce sends
WooCommerce’s email system is a separate class hierarchy from WordPress core’s. Each email type is a class extending WC_Email in includes/emails/; the full set is registered in WC_Emails::instance(). Each class exposes a settable $heading, $subject, and body template overridable from a theme’s woocommerce/emails/ folder. As of WooCommerce 10.8.1, the available email classes are visible in WooCommerce > Settings > Emails, where they can be individually enabled, disabled, and reconfigured.
When a new order is placed, WooCommerce generates two messages: a customer-facing order confirmation (class-wc-email-customer-processing-order.php) and an admin-facing notification (class-wc-email-new-order.php). The customer copy confirms payment received and lists the line items; the admin copy is a dashboard alert. As order status changes, further emails follow:
| Status | Class file | Recipient |
|---|---|---|
wc-processing |
class-wc-email-customer-processing-order.php |
Customer |
wc-on-hold |
class-wc-email-customer-on-hold-order.php |
Customer |
wc-completed |
class-wc-email-customer-completed-order.php |
Customer |
wc-cancelled |
class-wc-email-cancelled-order.php |
Administrator |
wc-refunded |
class-wc-email-customer-refunded-order.php |
Customer |
wc-failed |
class-wc-email-failed-order.php |
Administrator |
wc-on-hold fires when payment is pending external confirmation – common with bank transfer gateways, BACS, and some regional payment methods. If the payment gateway’s webhook is delayed or fails, the order can sit in on-hold indefinitely, generating the wc-on-hold email to the customer but no admin alert until the merchant checks the order queue manually. This is the WooCommerce email failure mode most frequently responsible for customer complaints about “missing order confirmation” messages: the customer received the on-hold email, interpreted it as a failed order, and placed a duplicate.
Turning off a WooCommerce email class at Settings > Emails suppresses it globally. Suppressing per order or per customer requires filtering woocommerce_email_enabled_{email_id}, where {email_id} is the class’s $id property.
The customer account emails handle the non-order lifecycle: account registration, password reset (WooCommerce’s own flow, separate from the core retrieve_password() flow), customer invoice, payment reminder, customer note, and refund notification.
Configuring and troubleshooting WooCommerce transactional email in depth is in the nanoPost WooCommerce transactional email guide.
Plugins that bypass the default sending path
Any plugin that calls wp_mail() produces transactional email in the normal WordPress sense: routed through PHPMailer, subject to the wp_mail filter, visible in a mailer plugin’s log. The problem class is plugins that call PHP’s mail() function directly, bypassing wp_mail() entirely.
A mailer plugin configured to route wp_mail() through an external relay does nothing for a plugin that skips wp_mail(). Those messages exit through the local MTA – or silently drop if the host has no MTA – regardless of what the mailer plugin is configured to do. Diagnosing this requires checking the relay’s sent log: if a message is missing there, the sending plugin bypasses wp_mail(). Check the plugin’s source for direct mail() calls. This bypass is uncommon in well-maintained modern plugins but remains a live problem with older contact form integrations and some payment gateway notification systems.
When wp_mail() itself fails – authentication rejected, relay unreachable, connection timed out – WordPress does not retry and does not log the failure unless a plugin hooks ![]()
wp_mail_failed. That action fires with a WP_Error argument containing the phpmailer exception message and the full message metadata. Without that hook, a failed send produces no visible error and no log entry. Most mailer plugins hook wp_mail_failed as part of their logging layer; if yours does not, the debug output available is limited to PHP error logs.
Contact form plugins, security plugins, membership plugins, and LMS plugins all generate transactional email in the structural sense: triggered by a user action, sent to one recipient about that action. Whether a specific message qualifies for the transactional legal carve-out depends on the jurisdiction’s definition, covered below.
The legal carve-out
Most jurisdictions that regulate marketing email explicitly exempt transactional and relationship messages. The legal boundary is not where most operators think it is.
CAN-SPAM (15 U.S.C. ch. 103): the statute defines a “transactional or relationship message” at §7702(17) as a message whose primary purpose is to facilitate, complete, or confirm a commercial transaction the recipient has agreed to; or to provide warranty, recall, safety, or security information about a purchased product; or to provide notification of changes in terms or features; or to provide account balance or subscription information; or to deliver goods or services the recipient is entitled to receive. Messages whose primary purpose is transactional are exempt from most CAN-SPAM requirements (opt-out mechanism, physical address, subject-line accuracy rules). The critical word is “primary purpose”: the FTC applies a reasonable-recipient standard to classify messages, not the sender’s stated intent. A sender cannot label an order confirmation as transactional and include substantial promotional content while expecting the transactional exemption to hold.
GDPR (Regulation (EU) 2016/679): transactional email typically falls under Article 6(1)(b) – processing necessary for the performance of a contract to which the data subject is party. A password reset, an order confirmation, or an account activation message is processing necessary to deliver the service the user requested. Consent under Article 6(1)(a) is not required. Recital 47 covers legitimate interest as an alternative legal basis, but Article 6(1)(b) is the cleaner ground for clear transactional triggers. The constraint is minimisation: the transactional message should contain what is necessary for the transaction, not additional profiling or unsolicited marketing content.
CASL (S.C. 2010, c. 23): section 6(6) lists messages exempt from CASL’s consent requirement. The list is categorical, not proportional: messages that facilitate, complete, or confirm a commercial transaction; provide warranty or safety information; provide factual updates about a subscription, membership, account, or comparable relationship; or provide information directly related to an employment relationship; or deliver entitled goods or services under a prior transaction. CASL’s exemptions are narrower than CAN-SPAM’s primary-purpose test – a message must fit a listed category, not merely score high on a primary-purpose weighing.
ePrivacy Directive (2002/58/EC), Article 13(2): the soft opt-in provision allows sending electronic marketing communications to existing customers where the message concerns similar products or services, provided the customer has a clear opportunity to object at the time of collection and in each subsequent message. This is not a transactional carve-out. It is an exception to the opt-in requirement for marketing to existing customers – a different instrument for a different category of message.
The promotional content edge case is where WordPress operators most often miscalculate. WooCommerce’s order email templates support configurable footers, and promotional content – coupon codes, upsell offers – is commonly added to the Completed order email by store operators. Under CAN-SPAM’s primary purpose test, a message where the transactional content dominates should remain classified as transactional. Under CASL, where the exemptions are categorical, any commercial promotional content in the message may require re-evaluating the applicable basis – the promotional portion sits outside the section 6(6) categorical exemption. The ePrivacy Directive’s Article 13(2) soft opt-in, where available, is the legal instrument for that promotional footer in EU-adjacent jurisdictions, not the transactional carve-out.
The sending stack
Every WordPress email – core, WooCommerce, plugin, provided it goes through wp_mail() – follows the same path: wp_mail() composes the MIME message via PHPMailer 6 (bundled at wp-includes/PHPMailer/) and hands it to a transport. By default, the transport is PHP’s mail() function, which delegates to the server’s local mail transfer agent.
That transport fails on most modern managed hosting. Hosts either disable outbound SMTP connections, omit the MTA entirely (so mail() returns false and messages vanish), or send from shared IP addresses with no DKIM signing. The fix is a mailer plugin – WP Mail SMTP, FluentSMTP, and similar – that replaces the mail() transport with an authenticated connection to an external relay such as Postmark or SMTP2GO. For the action-oriented walkthrough that puts plugin and relay in place, How to set up email on WordPress is the parent. The full delivery chain, the failure modes at each stage, and the DNS records that make it work are in How email works: A WordPress operator’s reference.
Every wp_mail() call blocks the PHP process until the SMTP handshake completes; on a WooCommerce site with high order volume, that per-order latency adds up. WordPress email queues covers when that matters and the plugins that address it. External relays expose delivery events – bounce notifications, complaint rates, delivery confirmations – via webhooks that can connect back into WordPress for logging and retry logic. Email service webhooks and WordPress covers that integration.

