WooCommerce ships with a queue of transactional mail: new order, processing, completed, refunded, customer invoice, password reset. Every single one of those is routed through WordPress'swp_mail() wrapper, which falls back to PHPmail() unless you intercept it. That is where the trouble starts.
PHP mail() talks to the local MTA (sendmail/postfix on the hosting server). That MTA relays from the shared hosting IP, which is almost always on a consumer blocklist. Gmail sees no DKIM, Outlook sees a mismatched envelope, and Mail.ru rejects outright. Admins stare at the WooCommerce log seeing "sent" and wonder why customers email support asking where the confirmation is.
Install an SMTP plugin, route through an authenticated relay, add SPF and DKIM for the sender domain, and seed-test every status email across Gmail, Outlook, Yahoo and Mail.ru before you declare victory.
Why PHP mail() kills WooCommerce deliverability
On shared hosting — SiteGround, Bluehost, Hostinger, any cPanel environment — the outbound path from PHP mail() is identical for every tenant on the IP. Your order confirmation goes out alongside someone else's newsletter blast alongside a third tenant's password reset. The IP reputation is the lowest common denominator.
- No DKIM signing: PHP mail() does not sign. Gmail requires DKIM for any volume, and WooCommerce at a few orders an hour already counts as volume.
- Envelope mismatch: the MAIL FROM is usually the cPanel user (
cpanel_user@server42.hostprovider.net), notorders@yourstore.com. That fails DMARC alignment the moment you publish a DMARC record. - Shared IP blocklisting: Spamhaus PBL, UCEPROTECT L3 and Sorbs routinely list shared hosting ranges. Your mail never reaches the filter — it bounces at the border.
- No retry intelligence: PHP mail() returns true if the local MTA accepted the message. What happens after that is invisible to WooCommerce.
Step 1: install an SMTP plugin
The three reliable choices for WooCommerce are WP Mail SMTP, Post SMTP and Fluent SMTP. All three hook wp_mail() and route through an authenticated relay of your choice — SendGrid, Postmark, SES, Brevo, Mailgun, or plain authenticated SMTP against Google Workspace / Microsoft 365.
For a store doing under 10k emails a month, the pragmatic pick is WP Mail SMTP with Brevo (free 300/day) or SendLayer. For higher volume, split the transactional stream to Postmark and keep it separate from any marketing list.
WP Mail SMTP + SendGrid example
// wp-config.php — do NOT put raw creds here, use env vars
define( 'WPMS_ON', true );
define( 'WPMS_MAIL_FROM', 'orders@yourstore.com' );
define( 'WPMS_MAIL_FROM_NAME', 'Your Store' );
define( 'WPMS_MAILER', 'sendgrid' );
define( 'WPMS_SENDGRID_API_KEY', getenv('SENDGRID_API_KEY') );
define( 'WPMS_SENDGRID_DOMAIN', 'yourstore.com' );Many tutorials paste the raw API key into wp-config.php. That file ships to git, ends up in backups, and leaks through plugin exploits. Use an env var via your host's panel or a secrets plugin. Rotate if a key was ever committed.
Step 2: authenticate the domain
An SMTP plugin without DNS authentication is a sticker on a broken window. You still need SPF, DKIM and DMARC on the domain in yourWPMS_MAIL_FROM. Every relay publishes the exact records — copy them into your DNS provider and wait for propagation.
DNS checklist
- SPF: one record at the apex. If you already use Google Workspace for corporate mail, merge the relay include into the existing SPF. Do not create a second TXT record at the root — SPF will fail permanent error.
- DKIM: the relay gives you a selector and CNAMEs. Publish exactly as shown. Verify with
dig TXT selector._domainkey.yourstore.com. - DMARC: start at
v=DMARC1; p=none; rua=mailto:dmarc@yourstore.com;. Move top=quarantineonce you confirm alignment is clean. - Return-Path / bounce domain: most relays require a CNAME so bounces align with your domain. This is what turns DMARC from green-with-one-yellow to green-green.
; example zone file additions for yourstore.com
yourstore.com. TXT "v=spf1 include:sendgrid.net include:_spf.google.com ~all"
s1._domainkey.yourstore.com CNAME s1.domainkey.u1234567.wl.sendgrid.net.
s2._domainkey.yourstore.com CNAME s2.domainkey.u1234567.wl.sendgrid.net.
_dmarc.yourstore.com TXT "v=DMARC1; p=none; rua=mailto:dmarc@yourstore.com; fo=1"
em.yourstore.com CNAME u1234567.wl.sendgrid.net.Step 3: clean up the WooCommerce template
The default WooCommerce email template is fine for a small store, but a few tweaks under WooCommerce > Settings > Emails tip the balance in your favour.
- Disable the header image for New Order and Customer Invoice — or keep it under 10 KB. Heavy banners push confirmations into Gmail Promotions.
- From name + from address: match your store brand.
orders@yourstore.comis better thanwordpress@yourstore.com. - Plain-text alternative: WooCommerce generates one automatically but many third-party plugins break it. Send a test and view source to confirm a
text/plainpart exists. - Footer: remove the "Powered by WooCommerce" default string. Replace with a support email and a physical address — that alone lifts trust signals.
Step 4: seed-test every status email
WooCommerce has a dozen trigger points. Each one uses a slightly different template and a different subject line. Testing only "New order" tells you nothing about whether "Refunded" or "Failed payment" will land. Walk through all of them.
- Place a real test order against a set of seed mailboxes covering Gmail, Outlook, Yahoo, iCloud, Mail.ru and Yandex.
- Mark it Processing. Check placement of the processing email.
- Mark it Completed. Check again.
- Refund. Check again.
- Trigger a password reset for one of the seed accounts. That uses the core WordPress template, not WooCommerce's — often a different failure mode.
Run an inbox placement test across 20+ seed mailboxes — see which WooCommerce emails hit inbox, which go to Promotions, and which get silently dropped. No signup.
Common traps after the fix
- Caching plugins: WP Rocket or LiteSpeed Cache can cache admin-ajax responses and break SMTP plugin debug logs. Exclude
/wp-admin/from cache. - Multisite: network activation of WP Mail SMTP is different from per-site activation. In multisite stores, set the relay at network level and let each site override from/name.
- Order action emails: third-party plugins (Order Delivery Date, Subscriptions, Bookings) often bypass wp_mail(). They call PHP mail() directly. Audit each plugin.