Fix WordPress emails going to spam

Out of the box WordPress hands every message to PHP's mail() function, which signs nothing, authenticates nothing and reports success whether or not the mail is ever delivered. That is why password resets, contact form notifications and WooCommerce order emails end up in spam or vanish outright. This guide covers what wp_mail() actually does, why the default wordpress@ From address breaks alignment, which hosts block which SMTP ports, and how to test a real send with Unspam.

Why WordPress emails land in spam.

01

PHP mail() cannot sign anything, and WordPress never asks it to

WordPress core never configures DKIM. The PHPMailer library it bundles can sign, and exposes DKIM_domain, DKIM_selector and DKIM_private, but nothing in core ever sets them, so the message leaves your server with no signature.

02

Your mail comes from wordpress@yourdomain.com and nobody owns that mailbox

When no From header is supplied, wp_mail() builds one from the site host: it reads the host from network_home_url(), strips a leading www., and prefixes wordpress@. The display name defaults to WordPress. Password resets, new user notifications, comment moderation mail and core update notices all go out under that address, and on most sites no mailbox exists behind it, so replies and bounces land nowhere. Set a real address with the wp_mail_from filter, or in WP Mail SMTP under WP Mail SMTP > Settings using From Email with Force From Email checked.

03

SPF gets checked against your host's server, not your domain

With PHP mail(), the envelope address is whatever your host's setup produces, commonly the web server user at the server's own hostname. Receivers evaluate SPF against that envelope domain, so unless your host forces an envelope address on your own domain (through PHP's sendmail_from, mail.force_extra_parameters, or a rewrite in its own MTA) your SPF record is not the one being consulted and SPF cannot align with the From domain for DMARC.

04

Your host blocks the port your SMTP plugin is dialing

Hosts block outbound SMTP to contain spam, and the rules differ per platform. Kinsta says you cannot use port 25 because its infrastructure blocks outbound port 25, and lists 587, 465 and 2525 as usable, while WP Engine says its infrastructure providers do not allow mail over port 25, allows 587 and 465, and recommends 2525. DigitalOcean blocks 25, 465 and 587 on Droplets by default including traffic through a Reserved IP, Google Cloud blocks port 25 to destinations outside your VPC, and AWS throttles port 25 from EC2 unless you request removal. Note that WP Mail SMTP's own host roundup lists WP Engine and Kinsta as hosts that do not block SMTP, which conflicts with what both hosts publish about port 25, so trust your host's documentation over any third party list.

05

Your contact form sends as the visitor who filled it in

The most common form setup puts the submitter's address in the From field so replies are easy. That means your server sends mail claiming to be from gmail.com or outlook.com, a domain that has never authorized your server, and DMARC fails on the visitor's domain rather than yours. WPForms' own docs tell you the From Email must be valid, should match your domain, and to avoid using the form submitter's address because it can look like a phishing attempt; the submitter's address belongs in Reply-To via a smart tag. WooCommerce says the same thing about order mail: a From address on a public domain like @gmail.com or @yahoo.com is likely to be spam foldered or blocked because of the mismatch between the declared sender and the actual sender.

06

You share a sending IP with every other site on the box

On shared hosting, PHP mail() leaves through an IP used by hundreds of neighboring sites, and one compromised WordPress install on that machine can get the whole IP listed. You have no control over that IP's reputation and usually no control over its reverse DNS either, while Google's guidelines require sending IPs to have valid forward and reverse DNS records. Routing through a provider moves you onto infrastructure whose reputation someone is paid to defend. Until then, take the sending IP from the Received headers of a message your site actually sent and run it through Unspam's on demand blocklist checker to see whether the neighbors have already cost you.

How WordPress authenticates your mail.

WordPress publishes no DNS records for you and signs nothing, so there is no sending identity to authenticate until you change how mail leaves the site. Fix the route first, then the records, in that order: SPF and DKIM records mean nothing while the message still goes out through PHP mail() with your host's envelope address on it.

record default the problem the fix
Sending route (SMTP plugin) wp_mail() calls PHPMailer's isMail(), so every message goes through PHP's mail() and your host's local MTA. Nothing signs the message and nothing owns the envelope address, so there is no identity for SPF, DKIM or DMARC to attach to. WP Mail SMTP's free version includes SendLayer, SMTP.com, Brevo, Gmail, Elastic Email, Mailgun, Mailjet, SendGrid, Postmark, SparkPost, SMTP2GO and Other SMTP, while Microsoft, Amazon SES and Zoho are marked Pro.
SPF Either absent or irrelevant: with PHP mail() the envelope sender is chosen by your host, so your own SPF record is not the one being checked. Receivers look up SPF for the envelope domain, which is your server's hostname, so your record cannot pass and cannot align with the From domain in DMARC. Once a provider owns the envelope, publish the include it documents in a single record on the sending domain, for example v=spf1 include:_spf.google.com ~all for Google Workspace. One SPF record per domain, no exceptions.
DKIM No signature whatsoever. Core bundles a PHPMailer that is capable of signing, but nothing in WordPress ever populates its DKIM signing properties. Google asks all senders for SPF or DKIM, and bulk senders (5,000 or more messages a day to Gmail) for both plus alignment. Unauthenticated mail can be marked as spam or rejected with a 5.7.26 error. Publish the CNAME or TXT selector records your chosen mailer issues, then confirm on a live send that the DKIM d= value is your own domain rather than the provider's shared one.
DMARC None. Nothing in WordPress publishes a policy or warns you that one is missing. With no record you get no reports of the unauthenticated mail leaving your domain. With p=reject published while the site still uses PHP mail(), you reject your own password resets. Publish v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com, confirm in the reports that your WordPress traffic now shows up aligned under the provider you chose, then tighten to quarantine and reject.

Once you have updated these records, confirm they pass with Unspam's free SPF checker, DKIM checker, and DMARC checker.

How to test a WordPress campaign with Unspam.

WP Mail SMTP's built in test at WP Mail SMTP > Tools > Email Test only exposes a Send To address and an HTML toggle, and its success message says the test email was sent, not where it landed. It proves your connection and credentials work, and its Domain Check looks up SPF, DKIM and DMARC for the From domain, but it cannot tell you which folder a real notification reaches. Run a real send instead.

  1. 01

    Get your Unspam seed address and Test ID

    Start a spam test or an inbox placement test in Unspam and copy the seed address it generates. Placement tests deliver to seed mailboxes at Gmail, Outlook, Yahoo and five other providers and report where each copy landed. An inbox placement test also issues a Test ID. You must paste it into the subject line or the body before you send, or the message reaches the seed mailboxes and is never matched to your test.

  2. 02

    Pick the real send you care about and find its From setting

    Each path has its own sender. WordPress core mail uses wordpress@yourdomain.com unless wp_mail_from or Force From Email overrides it, WooCommerce uses the From address under WooCommerce > Settings > Emails, and a form plugin uses the From Email in that notification. Testing core mail proves nothing about WooCommerce mail if the two send under different addresses.

  3. 03

    Put the Test ID somewhere that send will carry it

    For a contact form, submit the form yourself with the seed address in the email field and the Test ID pasted into the message field, so the notification carries it in the body. For WooCommerce, each email template has its own Subject field under WooCommerce > Settings > Emails, so append the Test ID there for the duration of the test and remove it afterwards. Do not test with a core password reset: you cannot inject the Test ID into that message.

  4. 04

    Trigger it on the live site, from the front end

    Send from the production host, not from a local install or a staging copy. The sending IP, the SMTP route and even the port availability differ between environments, and staging plugins that suppress outgoing mail will make the whole exercise silent. If you use a caching or firewall layer, submit the form as a logged out visitor so the request follows the same code path a customer's would.

  5. 05

    Read the report and check the identity, not just the score

    In Unspam, confirm the DKIM d= domain is yours, that SPF passed against a Return-Path you recognize rather than your web server's hostname, and that DMARC shows alignment. Then look at placement per provider, the spam score and its triggered rules, and the client previews. Fix what the report flags, then repeat the same send with a fresh Test ID.

The same test renders your campaign in 50+ real email clients, Gmail, Outlook, Apple Mail, iPhone, and Android among them, each in light and dark mode, via email client previews, so you confirm placement and rendering in one pass.

WordPress features that quietly affect delivery.

wp_mail() returning true means almost nothing

The return value tells you PHPMailer handed the message off without throwing, which for the PHP mail() path means the local MTA accepted it. Delivery, filtering and outright dropping all happen after that point. WordPress fires the wp_mail_failed action with a WP_Error only when PHPMailer throws an exception, so a message queued locally and quietly discarded looks exactly like a success to your site.

Every plugin has its own From field, and they win by default

It matters twice over, because a form plugin quietly injecting the visitor's address puts a From address you do not own on the message, which is exactly what WPForms tells you to avoid: it says using the submitter's address makes it look as though you are sending from an address you do not own, and can get the notification flagged as phishing.

HTML mail from WordPress often ships with no plain text part

wp_mail() defaults to a text/plain content type and explicitly sets PHPMailer's AltBody to an empty string. When a plugin filters wp_mail_content_type to text/html, PHPMailer switches to HTML but the alternative body stays empty, so the message goes out as a single text/html part. Content filters penalize that shape on its own: SpamAssassin's MIME_HTML_ONLY rule fires for a message that only has text/html MIME parts. Use a mailer or a template that builds a real multipart/alternative message.

The free plugin will not tell you it stopped sending

In WP Mail SMTP, Email Log, Email Reports, Email Alerts, Backup Connection, Smart Conditional Routing and Rate Limiting are all Pro features. On the free tier a mailer that starts rejecting your messages leaves no record in the admin, and the first sign is a customer asking why they never got their receipt. FluentSMTP includes email logs and failure alerts to Slack, Telegram or Discord for free, and WooCommerce separately records Sent, Failed, Disabled and Skipped attempts under WooCommerce > Status > Logs.

What real WordPress senders run into.

The deliverability problems WordPress senders hit most often, each with the fix that resolves it.

Password reset emails just vanish. WordPress says to check my inbox, and nothing arrives, not even in the spam folder.

wp_mail() returns true as soon as PHPMailer hands the message to PHP mail() and the local MTA accepts it. If that MTA has no route out, or the receiving side drops the mail because it is unsigned and its envelope belongs to a shared server, nothing anywhere in WordPress records the loss. The wp_mail_failed action only fires when PHPMailer throws.

The fix Install FluentSMTP or WP Mail SMTP, connect a provider, and check the plugin's log for that specific send. If the log shows a delivery and the mailbox still has nothing, the problem is at the receiver, so run a real send to an Unspam seed address and read the authentication results.

Our WooCommerce order confirmations go to spam for Gmail customers but arrive fine at our own Outlook inbox.

Your own mail server usually allowlists your domain or your server, so internal testing tells you nothing. Gmail applies the sender guidelines: SPF or DKIM at minimum, valid forward and reverse DNS on the sending IP, TLS in transit. PHP mail() from shared hosting fails the first, often fails the second, and gives you no visibility into either.

The fix Route WooCommerce mail through a provider, set the From address under WooCommerce > Settings > Emails to a mailbox on your own domain, and confirm on a real order that DKIM signs with your domain. Never test deliverability by sending to a mailbox on the same infrastructure you send from.

I installed WP Mail SMTP, the test email says success, and real form notifications still land in spam.

The Email Test tool sends its own message using the plugin's From Email. Your form plugin sets its own From address in the notification settings, so unless Force From Email is checked, the real notification goes out under a different sender, possibly one the provider has not verified and one your DNS records do not cover. Same site, two different sending identities.

The fix Check Force From Email under WP Mail SMTP > Settings, or set the form notification's From Email to the same verified address, and move the visitor's address into Reply-To with a smart tag. Then submit the form for real with an Unspam seed address and the Test ID in the message field.

Contact Form 7 worked for years, then Gmail started bouncing everything with a 5.7.26 error.

The form is putting the visitor's address in the From header, so your server sends mail claiming to be from gmail.com. Google's sender requirements tightened in February 2024, and unauthenticated mail can now be marked as spam or rejected outright with 5.7.26. Nothing changed on your site; the bar moved.

The fix Put an address on your own domain in From, and the submitter's address in Reply-To, which is what WPForms and WooCommerce both document. Then authenticate that domain properly through an SMTP provider so the From you now use actually passes SPF or DKIM alignment.

Moved the site to a new host and email stopped completely. Nothing in the logs, nothing in the plugin.

Outbound port policy is per platform and it does not travel with your files. Kinsta blocks outbound port 25 and offers 587, 465 and 2525. WP Engine does not allow port 25, allows 587 and 465, and recommends 2525. DigitalOcean blocks 25, 465 and 587 on Droplets by default, including traffic via a Reserved IP, and AWS throttles port 25 from EC2 by default. A blocked port surfaces as a connection timeout, not as a deliverability problem.

The fix Ask the host in writing which outbound ports are open for your plan, set the matching port and encryption pair in your plugin (TLS with 587, or SSL with 465), and if everything is blocked switch to a mailer that reaches the provider over HTTPS instead of SMTP.

WordPress deliverability, answered.

Why do my WordPress emails go to spam when I only send password resets and form notifications?

Volume is not what filters object to. wp_mail() hands the message to PHP mail(), which sends it unsigned, from an address nobody owns, with an envelope belonging to your web server rather than your domain. Google asks every sender, at any volume, to set up SPF or DKIM. A single unauthenticated password reset fails that bar just as clearly as a campaign would.

Do I still need an SMTP plugin if I already published SPF, DKIM and DMARC?

Yes, in almost every case. With PHP mail() your SPF record is not the one receivers check, because the envelope sender is your host's, and nothing in WordPress signs with your DKIM key. The records only start working once something owns the sending identity, which is what an SMTP plugin plus a provider gives you. Publishing DMARC while still on PHP mail() is useful only for the reports it generates.

WP Mail SMTP or FluentSMTP?

Both replace the wp_mail() path with an authenticated provider connection and both are free to install. FluentSMTP is fully free and includes email logs, failure alerts and routing across several connections by From address. WP Mail SMTP has a larger free mailer list including SendLayer, Brevo, Gmail, Mailgun, SendGrid and Postmark, but gates Email Log, Alerts, Backup Connection, Smart Conditional Routing and the Microsoft, Amazon SES and Zoho mailers behind Pro. Pick by which mailer you need and whether you want logging without paying.

My host blocks port 25. Which port or route should I use?

Try 587 with TLS first, then 465 with SSL, then 2525 if your provider offers it: Kinsta lists 587, 465 and 2525 as usable, and WP Engine allows 587 and 465 while recommending 2525. If every SMTP port is blocked, which is DigitalOcean's default on Droplets, use a mailer that talks to a provider API over HTTPS instead of SMTP. WP Mail SMTP's own fix for a connection failure is to correct the port and encryption pair or switch to an API based mailer.

Should I keep the wordpress@ address WordPress uses by default?

No. Replace it with a real, monitored mailbox on your sending domain so replies and bounce notices reach a human. Apply it with the wp_mail_from and wp_mail_from_name filters, or set From Email and From Name in WP Mail SMTP with Force From Email checked so plugins do not override it. Make sure the address is one your provider has verified, or the send will be rejected.

Does Unspam connect to my WordPress site to run a test?

No. Unspam never connects to your site, your host or your admin. There are exactly two integration points: a seed address you send to, and SMTP credentials you supply yourself if you want automated tests run on a schedule. Everything else in the report is read out of the message your site actually sent.

WordPress platform details were verified against publicly available documentation in August 2026 and may have changed since. WordPress is a trademark of its respective owner. Unspam is not affiliated with or endorsed by WordPress.

Test your next WordPress campaign before your subscribers do.