WordPress emits email from at least ten distinct code paths in core, plus one for every plugin and theme that calls wp_mail(). A single test from the mailer plugin’s settings page proves the SMTP connection works; it does not prove the right code path fires for each of those sources.
This is a reference test suite for operators who have already configured email and want to verify every emission point — after initial setup, after configuration changes, after a plugin update that touched the mail path, or in response to a report that a specific notification stopped arriving. The suite is organised by emitter category; each entry names the trigger, the observation points, and where to look when the test fails.
Two different tests, often confused
Every entry in this suite runs as one of two test types:
Send-test. The mailer plugin’s “Send a test email” button. Proves the SMTP connection is authenticated and the relay is accepting messages. Fails when the SMTP credentials are wrong, the TLS handshake fails, or the relay is unreachable. Pass result: the relay logs a 250 OK and the message arrives. This is a useful sanity check; it is not sufficient as a test suite by itself.
Trigger-test. Actually performing the action that fires the WordPress notification: placing a real test order, submitting the real form, creating a real user account. Trigger-tests fail for different reasons than send-tests: a plugin is hooked to suppress the action, a security plugin is throttling the trigger, the action hook was removed by a theme or other plugin, or the code path used by production traffic is different from the one being tested. A site can pass every send-test and fail trigger-tests silently.
The entries below are trigger-tests unless noted.
Three observation points
1. Did the trigger fire? The form’s success screen appeared. The WooCommerce order moved to “Processing.” The user registration returned a “check your email” message. If the trigger did not fire, the notification cannot have been emitted: investigate the plugin, not the mailer.
2. Did wp_mail() run, and what did it return? Check the email log. Three states: no entry (the action fired but something prevented wp_mail() from being called — a filter, a security plugin throttle, or conditional logic in the sending plugin), entry with Sent: No or a red status (the mailer rejected the message — read the error; usually a credential or network failure), entry with Sent: Yes (wp_mail() returned true and the message was handed to the relay for transmission).
3. Did the relay accept it, and did the recipient mailbox receive it? Check the transactional provider’s activity log: Postmark Activity, SendGrid Email Activity, Mailgun Logs, SES bounce/complaint notifications. The provider log shows whether the receiving server returned a 2xx acceptance, a soft bounce (4xx), or a hard bounce (5xx). Then check the actual inbox: inbox, spam, Promotions tab, Updates tab. A message can pass all three observation points and still land in spam; that is a deliverability problem, not a sending problem. For the methodology of that layer, see How to test whether WordPress email actually reaches the inbox. When the relay’s activity log is inaccessible or a connection failure is suspected, testing directly with swaks bypasses WordPress and the plugin layer entirely.
Preconditions
Two checks before running the suite.
wp_mail() must work for arbitrary email. Install WP Test Email, which adds a send-test under Tools > Test Email. Send to an address you control and confirm it arrives. Alternatively, use the mailer plugin’s own test button. If this fails, stop here and work through Troubleshoot WordPress email first: every trigger-test below will fail for the same reason.
An email log must be installed. Without one, observation point 2 is invisible. Check & Log Email captures the full payload, headers, and wp_mail() result for every call. Log Emails is a lighter alternative that captures subject, recipient, and status without the full body. The mailer plugin’s own log (available free in WP Mail SMTP, FluentSMTP, and Post SMTP) is also sufficient if it shows per-message send status.
WordPress core emails
WordPress core sends several notification emails directly through wp_mail(). Automatic update notification emails fire on the cron schedule and cannot be cleanly triggered without WP-CLI or a live update; skip them in a standard verification pass. The four below are triggerable on demand and together cover the core notification paths.
Administrator email change
Trigger: Go to Settings > General, change the Administration Email Address to a second address you control, and save. WordPress sends a confirmation link to the new address before the change takes effect. The current admin address receives a “your email address changed” notice after the confirmation is clicked.
Why test this first: It runs through wp_mail() immediately, with no plugin involvement, no cron dependency, and no external trigger. If this test fails, the SMTP connection is broken; nothing else in the suite will pass.
Observation: Check the email log for two entries (confirmation link to the new address, notice to the current address). If neither entry appears, investigate whether a plugin is filtering new_admin_email_content or suppressing the call to update_option_new_admin_email().
New user registration
Trigger: Go to Users > Add New. Leave “Send the new user an email about their account” checked. Save the new user.
Two emails fire from this one action. The new user receives a welcome email with a password-setup link (sent to the address in the Email field). The site administrator receives a “New User Registration” notification at the address in Settings > General > Administration Email Address.
These two emails can fail independently. The admin notification uses get_option('admin_email'); the user welcome uses the submitted email address. A domain with aggressive spam filtering may accept the admin notification and reject the user welcome, or vice versa. Check both entries in the log separately.
Note: If the site uses WooCommerce, MemberPress, or another plugin that overrides wp-login.php‘s registration form, test registration through the plugin’s own flow instead — the email template and the From address may differ from core’s.
Password reset
Trigger: Log out (or open a private window), go to wp-login.php?action=lostpassword, and submit the form for an account whose email address you control.
On a healthy SMTP connection the reset email typically arrives within 30 seconds. Anything beyond three minutes is typically a soft-retry indicator; check observation point 3 first (the relay’s activity log), then observation point 2 (the wp_mail() log entry).
The specific failure modes for this flow — the WooCommerce customer reset path, the security plugin throttle, the 24-hour key expiry, the default From address — are covered in Why WordPress password reset emails don’t arrive.
Comment moderation
Trigger: With Settings > Discussion > “Comment must be manually approved” enabled, post a comment on a published post as a logged-out visitor. Do not use the admin account email — the administrator email address is the notification recipient and submitting it as the commenter produces self-addressed test behaviour. Use a name and email not associated with any WordPress user.
The site administrator should receive a moderation notification at the Administration Email Address. If the email log shows no entry, confirm Settings > Discussion has “Email me whenever — a comment is held for moderation” checked. If only “Email me whenever — Anyone posts a comment” is checked (the always-on notification), moderation-specific notifications will not fire for held comments. The two checkboxes are independent; only the second is relevant for this test.
The notification does not fire if the comment is auto-approved by Akismet or another spam filter before the wp_insert_comment hook runs.
WooCommerce emails
WooCommerce ships a complete set of default email templates. They divide into admin-facing (received by the store administrator) and customer-facing. WooCommerce 9.x ships eleven default templates in a standard single-site install without extensions; verify the count in WC_Emails::instance() in woocommerce/includes/class-wc-emails.php for the installed version.
Before testing: Go to WooCommerce > Settings > Emails. Confirm the “From” name and “From” address fields are set to an address on the organisational domain. These fields set the From header for all WooCommerce templates. The effective priority is: SMTP plugin Force From (if enabled) > WooCommerce From > WordPress default. A WooCommerce store that has working WordPress mail and correct SMTP plugin settings will still emit WooCommerce emails from the wrong From if these two fields are blank or misconfigured and Force From is not enabled. If WooCommerce emails are landing in spam while non-WooCommerce emails are not, check the WooCommerce From field first; if Force From is also enabled in the SMTP plugin, confirm both.
Each template has a “Manage” button in WooCommerce > Settings > Emails. The Manage screen has a “Preview” button that sends a populated template version to the admin address without running the order code path. Use Preview for template/CSS verification only; it bypasses the order state machine and is not a trigger-test.
Admin-facing templates
| Template | Trigger |
|---|---|
| New order | Place a test order through the storefront using a payment gateway that completes immediately (Stripe test card 4242 4242 4242 4242, or the WooCommerce dummy “direct” gateway). |
| Cancelled order | Place a test order, then change its status to “Cancelled” from WooCommerce > Orders. |
| Failed order | Place a test order with a payment gateway configured to decline (Stripe test card 4000 0000 0000 0002). Note: the WooCommerce “Bank Transfer” gateway moves orders to on-hold, not failed. |
Customer-facing templates
| Template | Trigger |
|---|---|
| Order on-hold | Place a test order using the WooCommerce Direct Bank Transfer or Cheque Payment gateway, which produce an on-hold status by design. |
| Processing order | Same test order that triggers “New order” above (the gateway-completes-immediately path). |
| Completed order | Change any paid test order’s status to “Completed” from WooCommerce > Orders. |
| Refunded order | Refund a paid test order from the WooCommerce order detail screen. Both full and partial refunds fire this template. |
| Customer invoice / order details | Open any order, then under “Order actions” choose “Email invoice / order details to customer” and click Update. This is the only WooCommerce template the admin can manually re-send on demand. |
| Customer note | Add a customer note to a test order with the “Notify customer” checkbox checked, then click “Add.” |
| Reset password | Visit /my-account/lost-password/ and submit a registered customer’s email address. This is the WooCommerce-specific customer reset path: separate from the wp-login.php core reset path above; each fires its own email with its own template. |
| New account | Register a new customer account through /my-account/ (the WooCommerce shortcode path, not the WordPress Users > Add New path). |
After running the full WooCommerce table, confirm the email log has an entry for each template and that the “From” address in the log matches the domain on the sending SMTP account (not [email protected] and not a subdomain variant). One wrong From across the full template set usually means the WooCommerce “From address” field is blank and the WordPress default is bleeding through.
Contact forms
Contact forms are one of the most common sites of unreported email failure. The site owner tests the SMTP plugin, confirms password reset works, and assumes the contact form is fine — but the notification email goes to a separate address, uses a different From setting, and sometimes fires through a different internal hook. Test each form explicitly.
Test pattern for all contact form plugins: fill out the form on the page where it is embedded, as a real visitor would. Some plugins suppress sending notification back to the notification recipient when the submitter’s email matches the recipient’s — use an external address as the submitter to avoid this.
WPForms
Submit the embedded form. Open WPForms > Entries and confirm the submission appears. Then check the email log for the notification entry.
WPForms wraps wp_mail() with its own notification engine. A submission can appear in WPForms > Entries with no email sent if the notification was conditionally disabled, if a Smart Conditional Logic rule prevented the notification from firing, or if the “Send To” email address in the notification settings has a typo. The entries view confirms the form processed the submission; the email log confirms whether wp_mail() was called.
If an autoresponder is configured (the confirmation email to the submitter), test it as a separate submission: it uses its own From and To settings and can fail while the admin notification succeeds.
Gravity Forms
Gravity Forms’ Notifications screen (Forms > [form] > Settings > Notifications) has a “Send Test” button that sends the notification with a placeholder payload. This is a send-test; run it first to confirm the notification is enabled and the address is correct, then submit the real form for the trigger-test.
Gravity Forms also has a Logging addon (Gravity Forms > Settings > Logging, if the addon is installed) that captures notification attempts at the Gravity Forms framework level, independent of the email log plugin. If the email log shows nothing after a real submission, the Gravity Forms log can distinguish “the notification was disabled by conditional logic” from “the notification fired but wp_mail() was not called.”
Gravity Forms also has a Spam Entries tab in each form’s entry list. A submission marked spam by the form’s CAPTCHA or honeypot validation does not trigger notifications.
Contact Form 7
CF7 has no built-in notification log. The form’s success message confirms the submission was processed by WordPress; the email log is the only signal that wp_mail() ran. Submit the real form and check the email log entry.
If
Flamingo is also installed (the CF7 companion storage plugin), it logs every CF7 submission independently of email status. A Flamingo entry with no email log entry means the submission registered but CF7’s mail call did not reach wp_mail() — usually a misconfigured CF7 mail configuration or a plugin conflict.
Fluent Forms
Submit the embedded form. Open Fluent Forms > Entries, open the submission, and check the “Logs” tab inside the entry detail view. Fluent Forms logs notification dispatch at its own layer: this log shows whether the notification fired, which email address it targeted, and whether the send was queued or immediate.
Fluent Forms’ Conversational Forms use a different submission handler than standard inline forms. If both layouts are in use, test each separately.
Ninja Forms
Submit the embedded form. Open Ninja Forms > Submissions and confirm the entry appears. Ninja Forms’ “Emails & Actions” log (Ninja Forms > Settings > Emails & Actions, if the debug log is enabled) captures action dispatch; the wp_mail() log is the fallback if the Ninja Forms log is not available.
Ninja Forms supports multiple email actions per form — an admin notification and a user confirmation, for example. Each action is a separate wp_mail() call and can fail independently. Check the email log for one entry per configured action, not just one entry per submission.
Formidable Forms
Submit the embedded form. Open Formidable > Entries to confirm the submission registered. The free version of Formidable shows entries but does not have a notification log; the email log plugin is the observation point for whether wp_mail() fired. Formidable Pro adds Settings > Logs, which captures notification dispatch details.
LMS, membership, and community plugins
These plugins send email through wp_mail(), so the three observation points apply identically. The test pattern: enroll or transact as a test user through the same path real users take, then check the email log.
LearnDash. Email notifications live at LearnDash LMS > Settings > Notifications. Triggers: enroll a test user in a course (Users > [user] > LearnDash > Enrolled Courses), complete a quiz, complete a course. Each sends a separate template to the learner and, for completions, optionally to the group leader or admin.
LifterLMS. Notifications at LifterLMS > Settings > Notifications > Email. Triggers: enroll a test student (via LifterLMS enrollment tool or through a payment), complete a lesson, complete a course.
MemberPress. Email settings at MemberPress > Settings > Emails. Triggers: subscribe to a membership (use a free membership or a test payment gateway), expire or cancel a membership manually from MemberPress > Members > [member].
Paid Memberships Pro. Email settings at Memberships > Settings > Email. Same trigger pattern: enroll a test user via Memberships > [level] > Free trial, or via a test payment, then expire or cancel manually.
BuddyPress / BuddyBoss. Email settings at Settings > BuddyPress > Options > Activity Settings (BuddyPress) or BuddyBoss > Emails (BuddyBoss Platform). Triggers: send a private message to a test user, mention a test user in an activity post, accept a friend request. BuddyBoss Platform uses its own email template system; BuddyPress uses wp_mail() directly.
For any of these plugins, “no email log entry after a confirmed trigger” usually means the plugin’s own notification toggle is off. Check the plugin’s notification settings before investigating wp_mail() or the SMTP configuration.
Custom application email
Plugins and themes can call wp_mail() directly for notifications not covered by the categories above: low-stock alerts, scheduled reports, webhook-triggered receipts, security notifications. The email log catches all of them, but only if it is running when the trigger fires.
To enumerate the custom email a site is sending: leave the email log running for 24-72 hours of normal traffic, then review the log for subjects and recipients that do not map onto the categories above. Each one needs identification (which plugin, which hook or function) and a test pattern.
Cron-driven email (subscription renewal reminders, scheduled digests, low-stock alerts) fires through wp_mail() on a cron schedule. Via WP-CLI (requires SSH access):
wp cron event run --due-now
Or to trigger a specific hook:
wp cron event run woocommerce_low_stock_notification
Webhook and API-triggered email (an external service POSTs to the site, which sends a notification) requires the external service to actually fire, or a curl reproduction of the request, to test. The email log entry will show the trigger if wp_mail() was called; the log will be empty if the webhook handler returned early before reaching the mail call.
The headless-path trap
The tests above use the WordPress dashboard or a real browser form. A site that processes the same actions through the REST API or via external automation may not fire the same notification hooks.
Specific cases:
- User creation via REST API (
POST /wp/v2/users). Core does not callwp_new_user_notification()on REST-created users by default. The welcome email and admin notification that fire from Users > Add New do not fire from the API path. - WooCommerce orders via WC REST API (
POST /wc/v3/orders). Creating an order directly via API withstatus: processingdoes not trigger the WooCommerce order-state-machine emails the same way a payment gateway completion does. Whether the “Processing order” customer email fires depends on the WooCommerce version and theset_statuscall path. - Form submissions via external workflow tools (Zapier, Make, WP Webhooks, n8n, AutomatorWP). If the production flow processes submissions through an external connector rather than the WordPress form submission handler, the form plugin’s notification hook may not fire at all.
Test the same path the real users take. If the production flow is API-driven, run the test against the API endpoint. A dashboard test that passes does not confirm the API path works.
Three tests that cover most failures
Three tests cover the majority of customer-impacting failures:
1. Trigger a password reset. Covers the most common WP core notification, the most commonly-reported failure, and the core wp_mail() path. If this fails, stop everything else and diagnose it first.
2. If WooCommerce is installed: place a real test order and complete it. Covers the customer-facing order sequence and confirms the WooCommerce From settings are correct. If WooCommerce is not installed, submit the primary contact form instead.
3. Check the email log after 24 hours of normal traffic for Sent: No entries. Manual tests catch the paths you thought to test. The log catches the paths you did not. A single Sent: No entry after a day of traffic identifies a broken notification that no manual test would have surfaced, because it fires from a path that was not on the list.
These three do not replace the full suite for post-configuration verification.
