WordPress not sending emails is rarely a broken WordPress: the message is usually being created but never trusted. By default WordPress hands mail to PHP's mail() function on your web server, from an address like [email protected], with no SMTP login and often no DKIM signature, so services such as Gmail and Microsoft 365 often junk it or refuse it. The fix is to send through an authenticated mail service and publish SPF, DKIM and DMARC records for your domain. Below is the order we check things in, with the commands and DNS records, plus the WooCommerce settings that stop order emails on their own.
Why WordPress is not sending emails: what wp_mail() actually does
Every email WordPress sends, from password resets to contact form notifications to WooCommerce order confirmations, goes through one function: wp_mail(). It builds the message with the PHPMailer library and, unless something changes it, tells PHPMailer to use PHP's own mail(). That passes the message to whatever mail program the server has installed. On many hosts that program is restricted, rate limited or not configured to sign mail for your domain at all.
Two details from the WordPress developer reference for wp_mail() explain most of the confusion:
- The default sender is invented. If no "From" header is set, WordPress uses
wordpress@followed by your site's domain, with any "www." removed. That mailbox usually does not exist, and the core source code itself notes that some hosts block mail from it. - "Sent" does not mean "delivered". The reference says a true return value does not mean the user received the email; it only means the method used processed the request without errors. A plugin that says "Test email sent successfully" is telling you the same limited thing.
So there are really three different failures that all look like "WordPress is not sending emails":
- The email is never created. A form plugin is misconfigured, a WooCommerce email is switched off, or the order never reached the status that triggers it.
- The email is created but rejected on the way out. The server's mail program refuses it, the host blocks outgoing mail, or SMTP credentials are wrong. WordPress fires the
wp_mail_failedaction with the reason. - The email leaves the server and the receiving side distrusts it. It lands in spam, in a quarantine folder your customer never sees, or it is rejected outright because your domain's authentication fails. This is the most common case, and nothing inside WordPress will show it.
Each needs a different fix, so find out which one you have before changing anything.
WordPress not sending emails? Find where they stop
Check the basics in the dashboard
Under Settings, then General, confirm the Administration Email Address is one you read. If you use a contact form plugin, open the form's notification settings and check the "To" and "From" fields. The "From" should be an address on your own domain; the visitor's address belongs in "Reply-To". More on why further down.
Send a test from the command line
If you have SSH access, WP-CLI gives you a clean test that bypasses any form plugin and prints the actual error if sending fails:
# Sends one test email and prints any PHPMailer error.
# Replace [email protected] with an inbox you can check.
wp eval '
add_action( "wp_mail_failed", function ( $error ) {
echo "Failed: ", $error->get_error_message(), PHP_EOL;
} );
var_dump( wp_mail( "[email protected]", "WP-CLI mail test", "Sent from wp eval." ) );
'
Read the result like this:
bool(false)with a "Failed:" line: the message never left WordPress. The error text (could not instantiate mail function, SMTP connect failed, authentication failed) tells you whether it is the server's mail program or your SMTP settings.bool(true)but nothing arrives: the message left and was junked or rejected downstream. Check spam and any quarantine, then look at authentication, below.- It arrives: core sending works, so the problem is in the plugin that is supposed to send, or in its settings.
Read the headers of a message that did arrive
If a test reaches your spam folder, open it and view the original message (in Gmail, the "Show original" option). Look for the SPF, DKIM and DMARC results. A "fail" or "none" on any of them is your answer, and it is far more reliable than guessing from a plugin's success notice.
Fix the sending: use an authenticated SMTP or transactional email service
The durable fix is to stop relying on the web server's mail program and send through a service whose job is delivering mail. You have two kinds of option:
- Your existing mailbox provider over SMTP, such as Microsoft 365 or Google Workspace. Fine for a low-volume brochure site, but these accounts have sending limits and are designed for people, not applications.
- A transactional email service, the category built for application mail such as receipts and password resets. They give you SMTP or API credentials, DNS records to verify your domain, and delivery logs that show whether each message was accepted, bounced or marked as spam. For a store, this is what we recommend.
Most sites connect the service with an SMTP plugin, and that is a perfectly good route. If you prefer not to add a plugin, WordPress gives you the phpmailer_init action, which runs just before every send and hands you the PHPMailer object. A small must-use plugin can switch it to authenticated SMTP:
// In wp-config.php, above "That's all, stop editing".
// Use the credentials your mail provider gives you.
define( 'MG_SMTP_HOST', 'smtp.your-provider.example' );
define( 'MG_SMTP_USER', 'your-smtp-username' );
define( 'MG_SMTP_PASS', 'your-smtp-password' );
// In wp-content/mu-plugins/smtp.php
<?php
add_action( 'phpmailer_init', function ( $phpmailer ) {
$phpmailer->isSMTP();
$phpmailer->Host = MG_SMTP_HOST;
$phpmailer->Port = 587;
$phpmailer->SMTPSecure = 'tls'; // STARTTLS on port 587.
$phpmailer->SMTPAuth = true;
$phpmailer->Username = MG_SMTP_USER;
$phpmailer->Password = MG_SMTP_PASS;
} );
// Replace the default wordpress@ sender with a real address on your domain.
add_filter( 'wp_mail_from', function () {
return '[email protected]';
} );
add_filter( 'wp_mail_from_name', function () {
return 'Example Ltd';
} );
Keep the password in wp-config.php rather than in the database or the theme, and make sure that file is not readable by other users on the server. Run the WP-CLI test again afterwards: an authentication error now means the credentials are wrong, not the server.
SMTP alone does not finish the job, though. Some guides say an SMTP plugin "fixes spoofing"; it does not. It changes which server sends the mail. Whether receivers trust that server to speak for your domain is decided by DNS.
SPF, DKIM and DMARC for WordPress, explained simply
These are three DNS records that let a receiving server check that mail claiming to be from your domain really is. Since February 2024, Google's email sender guidelines require every sender to Gmail to pass SPF or DKIM, and anyone sending more than 5,000 messages a day to Gmail accounts to have SPF, DKIM and DMARC, with the From domain aligned. A small store is unlikely to hit 5,000 a day, but set all three anyway; it is the difference between the inbox and the junk folder.
| Record | What it proves | Where it goes |
|---|---|---|
| SPF | Which servers are allowed to send mail for the domain | One TXT record on the domain itself |
| DKIM | The message was signed by the domain and not altered in transit | A TXT or CNAME record at selector._domainkey, supplied by your mail service |
| DMARC | What receivers should do when SPF and DKIM fail, and where to send reports | A TXT record at _dmarc |
Example records for a domain whose staff mail is on Microsoft 365 and whose website sends through a transactional service. The provider's include and DKIM values are placeholders; use the exact ones your provider shows you.
; SPF: one record only, listing every service that sends as example.com
example.com. TXT "v=spf1 include:spf.protection.outlook.com include:spf.your-provider.example -all"
; DKIM: published for you by the mail service (often as a CNAME instead)
s1._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh...rest-of-public-key"
; DMARC: start in monitoring mode, with aggregate reports
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:[email protected]"
The -all at the end tells receivers to fail anything not listed. Use it once you are sure the list is complete; ~all (soft fail) is the cautious setting while you are still finding every service that sends as your domain.
The mistakes we see most often:
- Two SPF records. Adding a second
v=spf1record for the new mail service, instead of adding an include to the existing one. Microsoft's SPF setup guide is explicit: only one SPF record is allowed per domain, and multiple records make SPF fail with a permanent error. - Too many lookups. SPF fails if checking it takes more than 10 DNS lookups, and each include can contain more includes. Remove services you no longer use.
- Forgetting the website. The SPF record lists Microsoft 365 for staff mail, but not the server or service that sends the website's mail. Staff email is fine; every order confirmation fails.
- Jumping straight to
p=reject. Google's DMARC setup guide recommends starting withp=none, reading the reports, and only then moving to quarantine or reject. It also advises waiting 48 hours after setting up SPF and DKIM before adding DMARC.
Change DNS carefully. Copy the existing records somewhere before you edit, and never delete the MX records or the existing SPF record to "start fresh". Doing so can stop your staff mailboxes from receiving mail.
WooCommerce emails not sending: the store checks
WooCommerce sends through wp_mail(), so everything above applies. But a store has its own ways to stop an email before it is ever created, and WooCommerce's email troubleshooting guide starts with them. Work through these in order.
1. Did the order reach a status that sends email?
An order still in Pending payment triggers no customer email. That usually means the customer abandoned an external payment page or the card was declined. If you were paid and the order is still pending, the problem is the payment gateway failing to report back to your site, not email.
2. Is the email switched on?
Under WooCommerce, Settings, Emails, open each template (Processing order, Completed order, New order and so on) and confirm "Enable this email notification" is ticked. Check the recipient field on the admin emails too.
3. Is the "From" address on your own domain?
Below the list of emails, under the sender options, WooCommerce has its own "From" name and "From" address. If that is a Gmail, Yahoo or Outlook.com address, receivers can see your server claiming to be Gmail and will treat it accordingly. Use an address on your store's domain. If you want replies to go elsewhere, WooCommerce now has a separate Reply-to option for that.
4. What does the transactional email log say?
Current versions of WooCommerce log every transactional email attempt. Go to WooCommerce, Status, Logs, and open the source named transactional-emails. Each attempt is recorded as Sent, Failed, Disabled or Skipped, and sent or failed attempts on orders are also added as private order notes. "Failed" includes the reason WordPress reported. "Sent" with no email in the customer's inbox points you straight back to authentication and deliverability. These logs only cover WooCommerce's own emails; password resets and other WordPress mail need your mail service's logs or a mail logging plugin.
5. Are emails deferred and stuck in a queue?
By default WooCommerce sends its emails during the same request that changes the order. Since version 10.8 it also has an optional "Deferred emails" feature that sends them in the background through Action Scheduler, so checkout is not slowed down by the mail connection. It is off by default, and some stores or plugins turn deferral on through the woocommerce_defer_transactional_emails filter. If it is on and Action Scheduler is not running, emails queue up and never go out.
Check under Tools, Scheduled Actions (also under WooCommerce, Status), and search for the hook woocommerce_send_queued_transactional_email. A pile of past-due pending actions means the queue is not being processed, which usually comes back to WP-Cron not firing on a low-traffic or heavily cached site. You can process the queue manually while you fix the cause:
# Run only queued WooCommerce emails, then check the inbox.
wp action-scheduler run --group=woocommerce-emails
A real example
When we rebuilt a hacked Joomla store on WooCommerce, testing found that the new order emails were failing the sender check and being quarantined by Microsoft 365. We fixed the domain's SPF record before customers ever noticed. This is the kind of failure WordPress cannot report on its own, because it only knows the message was handed off, which is why we test the full path, from order to inbox, on every store launch.
Contact forms and the "From" address trap
Contact form plugins cause a specific, common failure. The form is set to send "From" the visitor's own email address, so a message from [email protected] arrives as if Gmail sent it, but it actually came from your web server. That is exactly what SPF and DMARC are built to catch, and the message is junked or dropped.
The fix is the same in every form plugin:
- Set "From" to a fixed address on your domain, such as [email protected].
- Put the visitor's email in the "Reply-To" field, using the form's field tag, so hitting reply still answers them.
- Send to an inbox you actually monitor, and test with an address outside your own domain.
Keep it working after you fix it
Email breaks quietly. A DNS change for a new marketing tool overwrites the SPF record, an SMTP password is rotated, a plugin update changes a form's sender, or the mail service suspends the account over a billing card. Nobody notices until a customer asks where their receipt is.
- Use a mail service with delivery logs, and look at them after any DNS or plugin change.
- Read your DMARC aggregate reports, or have someone read them for you. They show every source sending as your domain, including ones you forgot about.
- Place a real test order or submit a real form after every update cycle, and check it arrives.
That last check is part of what our maintenance plans do after tested updates. Plans start at $59 per month, and WooCommerce store maintenance is $299 per month. If you only need this one problem fixed, fixes start at $500, and every price is on the pricing page.
Common questions
How do I test if WordPress is sending emails?
Run wp eval with wp_mail() as shown above, or use a test-email plugin, and send to an address outside your own domain. Then check the inbox, the spam folder and the message headers. A success message only means WordPress handed the email off, not that it arrived.
Why do WordPress emails go to spam?
Usually because the domain cannot be verified: no DKIM signature, an SPF record that does not include the server that sent the email, or a "From" address on a domain you do not control, such as Gmail. Sending from your web server's shared mail program adds to the problem. Authenticated sending plus correct SPF, DKIM and DMARC fixes most cases.
Do I need an SMTP plugin for WordPress?
You need authenticated sending, and an SMTP plugin is the easiest way to get it. If you prefer fewer plugins, the phpmailer_init action does the same job in a few lines of code. Either way, it only works if the DNS records for your domain authorize that service.
Why is WooCommerce not sending order emails to customers?
Check that the order reached Processing or Completed, that the email is enabled in WooCommerce, Settings, Emails, and that the "From" address is on your domain. Then read the transactional-emails log under WooCommerce, Status, Logs. If it says Sent, the problem is deliverability, not WooCommerce.
Can I use my Gmail address as the From address?
Not for mail your website sends. Your server is not one of Gmail's servers, so the message fails Gmail's own authentication checks. Use an address on your website's domain and set your Gmail address as the Reply-To if that is where you want answers.
Next step
If order or form emails are going missing and you would rather hand it over, start a project and we will trace where the email stops and fix it, or put the site on one of our maintenance plans so it is checked after every update.
Want a senior developer to look at your site?
Tell us what you need. You get a written scope and a fixed price, usually within one business day.


