WordPress Not Sending Email: SMTP Setup That Actually Delivers

Was this helpful?

WordPress “sent” the email. Your customer didn’t get it. Support is now forwarding screenshots of empty inboxes, and the only concrete artifact you have is a plugin checkbox called “Use PHP mail()”.

Here’s the uncomfortable truth: email delivery is not a WordPress feature. It’s a distributed system with identity, reputation, DNS, policy, and a dozen ways to fail silently. The fix is almost always SMTP—but not the cargo-cult “install a plugin and pray” version. The fix is an SMTP setup you can observe, test, and operate.

Fast diagnosis playbook

If you’re on-call, you don’t have time to rediscover SMTP. You need to find the bottleneck quickly: WordPress? PHP? local MTA? network? remote relay? DNS policy? recipient filtering? Use this sequence.

First: confirm whether WordPress is even attempting to send

  • Trigger a known email: password reset for a test user, WooCommerce order email, or a form submission that logs an event.
  • Check application logs: plugin logs (if your SMTP plugin supports it), web server logs for the request, and WordPress debug log if enabled.
  • Decision: If there’s no attempt, you have an application/config problem. If there is an attempt, continue.

Second: identify the sending path

  • Is it PHP mail() to a local MTA? That’s “WordPress handed a message to the server.” It says nothing about delivery.
  • Is it SMTP direct to a relay? Better: you can authenticate, log, and trace failures.
  • Decision: If you can’t name the path in one sentence, you don’t have a system—you have vibes.

Third: locate the first durable error

  • Local MTA queue grows? Look at Postfix/Exim queue and logs.
  • Remote SMTP rejects? You need the remote error code (5xx/4xx) and reason.
  • Accepted but not received? Deliverability and policy: SPF/DKIM/DMARC alignment, reputation, content, throttling, or recipient-side filtering.
  • Decision: Don’t change five things at once. Capture the first solid error, then fix the cause behind it.

Paraphrased idea (attributed): Make failure visible; reliability starts with observing what the system actually does — Charity Majors (paraphrased idea).

What actually breaks when WordPress can’t send email

“WordPress not sending email” is usually one of these:

  1. No MTA or broken local MTA. Many modern VPS images don’t ship with a configured mail transfer agent. PHP can call sendmail, but there’s nothing sensible behind it.
  2. Provider blocks outbound SMTP. Some hosts block port 25 or throttle mail from shared IP ranges. Cloud providers do this to reduce abuse.
  3. SMTP auth fails. Wrong username, wrong password, wrong port, wrong encryption mode, wrong “From” identity, or wrong tenant settings (hello, Microsoft 365).
  4. DNS identity is missing or misaligned. SPF is too strict, DKIM isn’t set, DMARC is enforcing and alignment fails. Mail gets rejected or silently bulk-foldered.
  5. Reputation and policy issues. New domain/IP sending transactional mail looks suspicious. Recipient systems don’t care that it’s “just password resets.” They care about signals.
  6. Content triggers filters. Forms that send user-generated content can look like phishing, or include URLs that are on bad lists.
  7. Time and TLS problems. Bad system time breaks TLS validation. Old ciphers break modern SMTP endpoints. “Works on my laptop” because your laptop isn’t the server.

Joke #1: Email deliverability is like a nightclub bouncer: you can be on the list and still not get in because you “look suspicious.”

In production, I treat WordPress mail as a pipeline with checkpoints:

  • App checkpoint: WordPress calls wp_mail().
  • Transport checkpoint: plugin hands off to SMTP or PHP mail() hands off to local MTA.
  • Relay checkpoint: SMTP relay accepts (or rejects) the message.
  • Recipient checkpoint: recipient SMTP accepts (or rejects) from the relay.
  • Inbox checkpoint: filtering and policy decide where it lands, or whether it’s quarantined.

Your job is to find the first checkpoint where reality diverges from expectation. Everything after that is downstream noise.

Interesting facts (and why they still matter)

  • SMTP is older than the web. The core SMTP spec predates modern web hosting, which explains why it assumes a lot of trust and adds authentication later.
  • SPF was designed to stop forged envelope senders, not “From:” headers. Many folks expect SPF to validate what users see. It doesn’t—alignment via DMARC is what ties things together.
  • DKIM signs the message body and selected headers. That means some “helpful” relays that rewrite content can break signatures and tank delivery.
  • DMARC exists because SPF and DKIM alone weren’t enough. DMARC adds policy and reporting, and it formalizes alignment so receivers can enforce.
  • Greylisting is still a thing. Some recipients temporarily reject first-time senders (a 4xx). Good MTAs retry; naive implementations declare failure and give up.
  • Shared hosting IPs often inherit bad reputation. Your WordPress site may be well-behaved, but your IP neighbor might be an unstoppable newsletter cannon.
  • Port 25 is commonly blocked by cloud providers. Not because they hate you—because spam is an externality and they pay for it in reputation and abuse response.
  • Transactional and marketing mail have different expectations. Password resets need fast delivery and high trust; bulk mail needs warm-up and strict compliance. Mixing them hurts both.
  • “From” address matters more than people think. Many receivers apply domain reputation at the header From level, not only the sending IP.

Pick your SMTP architecture (and avoid the tempting bad ones)

Option A: Use a dedicated SMTP relay service (recommended)

This is the grown-up answer for most WordPress sites that matter. Your WordPress host runs the app. A dedicated relay handles deliverability, retries, bounce processing, feedback loops, and IP reputation.

Pros: best deliverability, real logs, predictable throttling behavior, easier domain authentication support. Cons: costs money, needs DNS work, credentials to manage.

Pick this when: you send password resets, order confirmations, membership emails, or anything users will scream about when it fails.

Option B: Use your corporate mailbox provider’s SMTP (sometimes okay)

Gmail/Google Workspace or Microsoft 365 SMTP can work for low-volume transactional mail, but expect constraints: rate limits, “from” restrictions, OAuth vs basic auth changes, tenant policies, and occasional surprises.

Pros: already paying for it, familiar admin console. Cons: limits, auditing friction, and it’s easy to misconfigure identities so DMARC fails.

Option C: Run Postfix on the server and deliver directly (rarely worth it)

Yes, you can run your own outbound MTA and send directly to recipient MX hosts. If you’re reading this because WordPress mail is flaky, you probably don’t want to also become an email reputation engineer.

Pros: full control. Cons: reputation management, blocklists, reverse DNS, abuse handling, constant tuning. Also: you’ll be blamed for spam you didn’t send.

The bad default: PHP mail() into the void

PHP mail() isn’t “wrong,” it’s just the least observable transport. It’s a handoff to whatever is configured as sendmail_path. In shared hosting it might work. In modern managed environments it often points to nothing or to a local relay with zero deliverability work.

Hands-on tasks: commands, outputs, decisions (12+)

These tasks are designed to be run on the WordPress host (or container host) with shell access. Each includes: command, sample output, what it means, and the decision you make.

Task 1: Confirm WordPress host can resolve DNS correctly

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
 resolv.conf mode: stub
Current DNS Server: 1.1.1.1
       DNS Servers: 1.1.1.1 8.8.8.8

Meaning: DNS resolution is configured and points to reachable resolvers.

Decision: If DNS is broken or points to an internal resolver that can’t resolve public records, fix DNS first. SMTP troubleshooting without DNS is performance art.

Task 2: Check whether outbound SMTP ports are reachable

cr0x@server:~$ nc -vz smtp.mailprovider.example 587
Connection to smtp.mailprovider.example 587 port [tcp/submission] succeeded!

Meaning: Network path to the relay on port 587 (submission) is open.

Decision: If this fails, you’re dealing with firewall rules, cloud egress controls, or provider restrictions. Use 587/465 with authentication; don’t fight port 25 blocks unless you enjoy abuse tickets.

Task 3: Verify system time (TLS depends on it)

cr0x@server:~$ timedatectl
               Local time: Sat 2025-12-27 13:05:41 UTC
           Universal time: Sat 2025-12-27 13:05:41 UTC
                 RTC time: Sat 2025-12-27 13:05:41
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Meaning: Clock is synced; TLS cert validation won’t fail because the system thinks it’s 1970.

Decision: If unsynchronized, fix NTP. Random TLS failures will waste your afternoon.

Task 4: Identify whether a local MTA is present

cr0x@server:~$ command -v sendmail; systemctl status postfix 2>/dev/null | head
/usr/sbin/sendmail
Unit postfix.service could not be found.

Meaning: A sendmail binary exists (maybe from msmtp or nullmailer), but Postfix isn’t installed.

Decision: If you’re relying on PHP mail(), you need to know what /usr/sbin/sendmail actually is. If it’s missing or inert, switch to SMTP via a plugin or install/configure a proper MTA.

Task 5: See what PHP thinks “sendmail” is

cr0x@server:~$ php -i | grep -E '^sendmail_path|^mail\.force_extra_parameters'
sendmail_path => /usr/sbin/sendmail -t -i => /usr/sbin/sendmail -t -i
mail.force_extra_parameters => no value => no value

Meaning: PHP will call /usr/sbin/sendmail -t -i.

Decision: If you’re not intentionally operating a local MTA, stop using this path. It’s unlogged by default and fails in creative ways.

Task 6: If Postfix exists, inspect the mail queue

cr0x@server:~$ mailq
-Queue ID-  --Size-- ----Arrival Time---- -Sender/Recipient-------
3F2A91234A      2213 Sat Dec 27 12:58:02  wordpress@example.com
                                         user@gmail.com
                                         (connect to gmail-smtp-in.l.google.com[142.250.102.27]:25: Connection timed out)
-- 1 Kbytes in 1 Request.

Meaning: Messages are queued and can’t reach recipient MX on port 25 (timeout).

Decision: Stop trying to deliver direct-to-MX from this host. Use an authenticated relay on 587/465. The queue is telling you the network path is blocked.

Task 7: Read mail logs for SMTP errors (Postfix example)

cr0x@server:~$ sudo tail -n 30 /var/log/mail.log
Dec 27 12:58:02 server postfix/smtp[21877]: connect to gmail-smtp-in.l.google.com[142.250.102.27]:25: Connection timed out
Dec 27 12:58:32 server postfix/smtp[21877]: connect to gmail-smtp-in.l.google.com[2607:f8b0:400e:c06::1b]:25: Network is unreachable
Dec 27 12:59:02 server postfix/qmgr[901]: 3F2A91234A: from=<wordpress@example.com>, size=2213, nrcpt=1 (queue active)

Meaning: Consistent timeouts and IPv6 unreachable. This is not a WordPress bug.

Decision: Configure Postfix to relay through a smarthost on 587, or disable IPv6 if it’s misrouted. Better: skip local MTA and use SMTP plugin directly to a relay.

Task 8: Test SMTP authentication and TLS with OpenSSL

cr0x@server:~$ openssl s_client -starttls smtp -connect smtp.mailprovider.example:587 -servername smtp.mailprovider.example -crlf
CONNECTED(00000003)
depth=2 C=US O=Example CA CN=Example Root CA
verify return:1
depth=1 C=US O=Example CA CN=Example Issuing CA
verify return:1
depth=0 CN=smtp.mailprovider.example
verify return:1
---
250 STARTTLS
250 AUTH PLAIN LOGIN
250 PIPELINING
250 SIZE 10485760

Meaning: TLS negotiation works; server advertises AUTH mechanisms.

Decision: If cert verification fails, fix CA bundle or intercepting proxy. If STARTTLS isn’t offered, you’re on the wrong port or the provider requires implicit TLS (465).

Task 9: Validate SPF record presence for your sending domain

cr0x@server:~$ dig +short TXT example.com | grep -i spf
"v=spf1 include:_spf.mailprovider.example -all"

Meaning: Domain has an SPF record authorizing your relay.

Decision: If missing, add it. If it ends with -all and you’re not including the relay, recipients will reject or spam-folder you. If there are multiple SPF records, fix that too (see Task 10).

Task 10: Detect multiple SPF records (a common self-own)

cr0x@server:~$ dig +short TXT example.com | grep -i "v=spf1" | wc -l
2

Meaning: Two SPF records exist. Receivers treat that as a permanent error (PermError) and SPF effectively fails.

Decision: Merge SPF mechanisms into a single record. Do not publish multiple v=spf1 TXT records.

Task 11: Confirm DKIM records exist (selector-based)

cr0x@server:~$ dig +short TXT s1._domainkey.example.com
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."

Meaning: DKIM public key is present for selector s1.

Decision: If absent, enable DKIM in your relay provider and publish the TXT record exactly as given. DKIM is not optional anymore for consistent delivery.

Task 12: Confirm DMARC policy and alignment expectations

cr0x@server:~$ dig +short TXT _dmarc.example.com
"v=DMARC1; p=quarantine; adkim=s; aspf=s; rua=mailto:dmarc@example.com"

Meaning: DMARC is enforcing quarantine with strict alignment for both DKIM and SPF.

Decision: Strict alignment means your visible From: domain must match the authenticated domains. Configure WordPress “From Email” and relay identity to align, or relax alignment if you understand the implications.

Task 13: Check reverse DNS (PTR) for your sending IP (if you send directly)

cr0x@server:~$ dig +short -x 203.0.113.10
mail.example.com.

Meaning: PTR exists. Many receivers distrust IPs without rDNS.

Decision: If you’re delivering directly, fix PTR with your ISP/cloud provider. If you’re relaying via SMTP provider, this matters less for your host and more for the relay’s IPs.

Task 14: Observe WordPress/PHP errors around mail sending (PHP-FPM)

cr0x@server:~$ sudo tail -n 40 /var/log/php8.2-fpm.log
[27-Dec-2025 12:57:51] WARNING: [pool www] child 18234 said into stderr: "PHP Warning:  fsockopen(): Unable to connect to smtp.mailprovider.example:587 (Connection timed out) in /var/www/html/wp-includes/PHPMailer/SMTP.php on line 214"
[27-Dec-2025 12:57:51] WARNING: [pool www] child 18234 said into stderr: "SMTP connect() failed."

Meaning: Application attempted SMTP but timed out—network path or firewall.

Decision: Fix egress firewall/security groups, NAT, or host provider restrictions. Don’t keep tweaking plugin settings when the TCP handshake never completes.

Task 15: Quick end-to-end SMTP test using swaks (if installed)

cr0x@server:~$ swaks --to user@gmail.com --from wordpress@example.com --server smtp.mailprovider.example --port 587 --auth LOGIN --auth-user wordpress@example.com --auth-password 'REDACTED' --tls
=== Trying smtp.mailprovider.example:587...
=== Connected to smtp.mailprovider.example.
<= 220 smtp.mailprovider.example ESMTP ready
=> EHLO server
<= 250-AUTH PLAIN LOGIN
=> AUTH LOGIN
<= 235 2.0.0 Authentication successful
=> MAIL FROM:<wordpress@example.com>
<= 250 2.1.0 Ok
=> RCPT TO:<user@gmail.com>
<= 250 2.1.5 Ok
=> DATA
<= 354 End data with <CR><LF>.<CR><LF>
=> .
<= 250 2.0.0 Accepted for delivery
=== Connection closed with remote host.

Meaning: SMTP relay accepted the message. Now you’re in deliverability territory, not transport.

Decision: If accepted but not received, inspect relay logs, check spam folders, verify SPF/DKIM/DMARC alignment, and ensure the “From” matches your authenticated domain.

WordPress SMTP configuration that survives production

WordPress uses PHPMailer under the hood. The classic failure mode is that WordPress is configured to “send” mail but the transport is either missing (no MTA) or untrusted (shared IP with no authentication). SMTP fixes both by making sending explicit.

Choose one SMTP plugin—and treat it like infrastructure

I’m not here to argue brands. Pick a reputable SMTP plugin that:

  • supports authenticated SMTP with TLS
  • can log send attempts and errors
  • lets you set a consistent From name/email
  • supports OAuth if your provider requires it

Configuration rules that prevent the usual disasters

  1. Use port 587 with STARTTLS unless your provider explicitly requires 465 (implicit TLS). Port 25 is for server-to-server and often blocked.
  2. Set From Email to a real mailbox or authorized sender in the same domain you authenticate. Don’t use wordpress@localhost or no-reply@yourdomain unless your relay permits it and your DNS aligns.
  3. Force From Email and From Name for transactional mail. Plugins and themes love overriding headers; that breaks alignment and creates “random” deliverability issues.
  4. Don’t use a personal mailbox credential for a website. Use a service account or relay API key. Rotate it. Store it in your secret manager if you have one.
  5. Turn on plugin logging if available, at least during setup. You want timestamps and SMTP error codes.
  6. Separate transactional from marketing if volume grows. Even if it’s the same provider, use separate subdomains or sending identities so one can’t poison the other.

Identity: the “From” address is not a styling choice

For DMARC-aligned mail, the visible header From: domain must align with either:

  • SPF-aligned domain (the envelope sender / return-path domain), or
  • DKIM d= domain (the domain that signs the message)

If you authenticate SMTP as wordpress@example.com but your plugin sets From as wordpress@some-other-domain, DMARC can fail even if SMTP auth succeeds. This is why “SMTP works but emails still disappear.”

Joke #2: The “From” header is like a name badge at a conference—if it doesn’t match your registration, security gets interested.

DNS and domain authentication: SPF, DKIM, DMARC

SMTP gets mail out the door. Authentication gets it accepted—and treated like a responsible citizen once it arrives. In 2025, you should assume major mailbox providers are skeptical by default.

SPF: authorize senders, don’t overcomplicate it

SPF is a TXT record that says “these hosts/IPs are allowed to send mail for my domain.” Receivers check the connecting IP against that policy.

Rules that keep SPF sane:

  • Publish exactly one SPF record per domain.
  • Prefer include: mechanisms for your relay provider; avoid listing dozens of IPs unless you own them.
  • Keep an eye on the DNS lookup limit (10). Too many includes can cause SPF PermError.
  • Decide on policy: ~all (softfail) during migration; -all (fail) once you’re sure.

DKIM: integrity and domain identity

DKIM signs outbound mail with a private key; recipients verify with your published public key. This proves the message wasn’t modified in transit and ties it to a domain.

Operational notes:

  • Use 2048-bit keys where supported.
  • Rotate selectors periodically (e.g., s1, s2). Keep old selectors around during overlap.
  • Beware of “helpful” plugins or relays that rewrite HTML or add footers; it can break DKIM unless the signing happens after rewriting (providers usually handle this correctly).

DMARC: policy, alignment, and the difference between “sent” and “trusted”

DMARC tells receivers what to do if SPF and DKIM checks don’t align with the visible From domain, and where to send reports.

Practical DMARC progression:

  1. Start with p=none to measure without breaking mail.
  2. Move to p=quarantine once you’ve fixed obvious misalignment and rogue senders.
  3. Use p=reject when you’re confident you’re not blocking legitimate senders (and you’ve inventoried all systems sending as your domain).

Alignment traps in WordPress specifically

  • Contact form plugins sometimes set From to the visitor’s email. That’s a DMARC fail waiting to happen. Use Reply-To for the visitor instead.
  • Multisite setups may vary From domain per site. If you can’t authenticate all of them, you’ll have inconsistent delivery.
  • Third-party marketing tools might also send from the root domain. Coordinate SPF/DKIM/DMARC or split subdomains.

Deliverability reality: reputation, rate limits, and content

Once SMTP accepts the message, you’re in the land of heuristics, reputation systems, and anti-abuse policies. It’s not personal. It’s math.

What “accepted for delivery” actually means

When your relay says “250 Accepted,” it means the relay accepted responsibility to attempt delivery. It does not guarantee inbox placement. It doesn’t even guarantee the recipient accepted it—just that the relay will try.

Reputation: your domain and your sending behavior

Mailbox providers build reputation from signals like:

  • consistent authentication (SPF/DKIM/DMARC alignment)
  • low bounce rates
  • low complaint rates (users hitting “spam”)
  • consistent volume patterns (sudden spikes look like compromise)
  • content quality and link reputation

Rate limiting and transient errors

Many SMTP failures are 4xx temporary deferrals: “try later.” A good relay retries with backoff. A bad setup marks it failed instantly and you never notice except customers complaining.

Content: WordPress emails can be filter bait

Common triggers:

  • forms that allow arbitrary user content, including suspicious URLs
  • messages with poor text-to-HTML ratio or weird encodings
  • subject lines that look like phishing (“Urgent: Account Verification”)
  • sending “From” a domain with no website alignment (brand-new domain, no history)

If your message includes user-generated content, treat it as untrusted input. Sanitize it. Limit links. Consider sending a minimal notification with a link to view the message inside your authenticated web app.

Three corporate mini-stories from the trenches

Mini-story #1: The incident caused by a wrong assumption

The setup looked normal: WordPress on a managed VPS, a contact form plugin, and a “From” address set to support@company.example. The team assumed that because the website was using the company’s domain, email authentication “must already be fine.” They’d set up SPF years ago for their newsletter platform and called it done.

Then a quiet change happened: DMARC policy moved from p=none to p=quarantine after a security review. Nobody told the web team, because of course nobody told the web team. Suddenly, contact form emails stopped showing up for a subset of recipients—mostly corporate inboxes with stricter filtering.

The first response was to blame WordPress. The second was to reinstall the plugin. The third was to “just use PHP mail().” None of that mattered. The actual issue: the contact form plugin set the visitor’s email as the header From (so replies looked nice). That meant DMARC alignment failed for every message where the visitor used a domain with p=reject (which is… a lot of them).

The fix was boring: keep From as support@company.example, set Reply-To to the visitor. Add DKIM signing via a proper relay. The incident ended not with a heroic patch, but with an email header that stopped lying.

Mini-story #2: The optimization that backfired

A different org had a high-traffic WooCommerce store. They were paying for an email relay and decided to “optimize” by sending password resets and order confirmations through their own Postfix to save a few dollars. They had an IP, they had a server, and email is “just SMTP,” right?

It worked for about two weeks. Then deliverability degraded. Some major mailbox providers started deferring with 4xx rate limits. Others bulk-foldered everything. The store’s support queue filled with “I didn’t get my receipt.” Worse: password reset emails intermittently vanished, creating account lockouts and chargebacks because customers couldn’t access order status.

The team tried to tune Postfix, then tried different HELO names, then tried sending from a different domain. They were chasing symptoms. The real issue was reputation and behavior: a new sending IP with spiky volume patterns, no established trust, and occasional bounces from typoed addresses.

They rolled back to the relay provider for transactional mail and kept Postfix only as a local submission point to the relay (smarthost), not as a direct-to-MX sender. The “optimization” saved money until it didn’t. The backfired part wasn’t technical complexity—it was underestimating how much receivers care about sender history.

Mini-story #3: The boring but correct practice that saved the day

A SaaS company ran multiple WordPress instances for marketing sites and documentation portals. They’d been burned before, so they standardized email sending: every WordPress used the same relay provider, separate sending domain (mail.company.example), and enforced From addresses. They also kept SMTP logs centralized.

One Monday, password reset emails slowed down. Not stopped—slowed. The helpdesk saw a pattern: Gmail users got mail, some corporate domains didn’t. The SRE on duty didn’t touch WordPress. They looked at relay logs and saw increasing 4xx deferrals from a specific recipient domain family.

Because they had DMARC reports and consistent DKIM signing, they could rule out authentication quickly. The deferrals were rate limiting due to a recipient-side incident. Their relay retried automatically with backoff, and delivery caught up within hours.

The saving move wasn’t cleverness. It was that they’d built observability and consistency into “boring” email infrastructure, so the incident was a status update, not a fire drill.

Common mistakes: symptom → root cause → fix

1) “WordPress says sent, but nothing arrives”

Root cause: Using PHP mail() without a functioning MTA, or with a local MTA that can’t deliver (blocked port 25, no relay, queue stuck).

Fix: Configure authenticated SMTP to a relay on 587/465. If you must use a local MTA, configure it to relay via a smarthost and monitor the queue.

2) “SMTP works in plugin test email, but contact form emails fail”

Root cause: Contact form plugin sets From to the visitor’s address; DMARC alignment fails or relay rejects spoofing.

Fix: Set From to your domain (aligned with DKIM/SPF). Put visitor email in Reply-To. Most form plugins have a setting for this.

3) “Authentication failed” or “535 5.7.3 Authentication unsuccessful”

Root cause: Wrong credentials, wrong auth method, basic auth disabled, or provider requires app password/OAuth.

Fix: Use provider-approved method: app password, SMTP relay credential, or OAuth. Confirm port/encryption. Stop reusing personal mailbox passwords.

4) “Connection timed out” to SMTP host

Root cause: Egress firewall, security group restrictions, outbound SMTP blocked by hosting provider, wrong DNS, or broken IPv6 route.

Fix: Test with nc and openssl s_client. Allow egress to relay on 587/465. Disable broken IPv6 or fix routing.

5) Emails go to spam for major providers

Root cause: Missing or failing DKIM, SPF PermError (multiple SPF records), DMARC policy misalignment, or poor reputation.

Fix: Publish correct SPF (single record), enable DKIM, add DMARC (start with p=none), ensure From aligns with authenticated domain. Consider a reputable relay and warm-up patterns.

6) “Some recipients get mail, others never do”

Root cause: Recipient-specific filtering, greylisting, rate limiting, or blocklisting of the sending IP/domain.

Fix: Use a relay with retry/backoff and logs. Check relay event logs for deferrals and bounces. Don’t assume uniform behavior across recipients.

7) WordPress password reset emails fail after a site migration

Root cause: New host’s IP reputation, missing DNS records in the new DNS zone, or sending from a domain not authorized by the new relay.

Fix: Re-verify SPF/DKIM/DMARC in the active DNS zone. Use the same relay credentials and sending domain. Confirm the From address didn’t revert to a default.

8) “It worked until we enabled caching/CDN/security plugin”

Root cause: Not the cache. Usually it’s a coincidental change: plugin update, credentials rotated, provider policy changed, outbound firewall hardened, or cron stopped running so queued notifications never fire.

Fix: Verify wp-cron/real cron, SMTP connectivity, and plugin logs. Correlate timestamps with deploys and credential changes.

Checklists / step-by-step plan

Step-by-step: from “no email” to “reliably delivered”

  1. Decide the architecture: use a dedicated SMTP relay for transactional mail.
  2. Pick the sending identity: e.g., no-reply@example.com or support@example.com—but make it real and aligned.
  3. Configure WordPress SMTP plugin: host, port 587, STARTTLS, auth enabled, force From.
  4. Publish SPF: single record including the relay provider.
  5. Publish DKIM: selector record(s) as provided by the relay.
  6. Publish DMARC: start p=none with reporting, then move toward enforcement once stable.
  7. Run connectivity tests from the server: nc + openssl s_client to the relay.
  8. Send a test email: confirm relay accepts and recipient receives.
  9. Fix alignment for forms: From = your domain, Reply-To = visitor.
  10. Enable logging: plugin logs + relay logs; keep them long enough to debug incidents.
  11. Set up monitoring: alert on sustained send failures, queue growth (if applicable), and spikes in bounces.
  12. Document the “known good” config: credentials location, DNS records, and the decision of why this relay exists.

Operational checklist: what to check during an incident

  • Can the server reach the relay on 587/465?
  • Did credentials rotate or expire?
  • Any recent WordPress/plugin updates?
  • Any DNS changes (SPF/DKIM/DMARC) or zone cutovers?
  • Relay logs: rejects (5xx) vs deferrals (4xx) vs accepted.
  • Recipient-side: are specific domains affected?

Hard rules (because you will be tempted)

  • Do not send contact form mail with visitor as From. Use Reply-To.
  • Do not rely on PHP mail() in environments you don’t control. If you can’t read mail logs, you don’t have a system.
  • Do not publish multiple SPF records. Ever.
  • Do not use shared mailbox credentials in production. Make a service identity.

FAQ

1) Why does WordPress “work” on one host but not another?

Because PHP mail() depends on the host’s MTA and network policies. One host has a configured relay; another has nothing behind /usr/sbin/sendmail, or outbound SMTP is blocked.

2) Should I use port 465 or 587?

Use 587 with STARTTLS unless your provider explicitly says 465. Both can be secure; 587 is the modern submission default and tends to behave predictably through firewalls.

3) My SMTP test says “success,” but emails still don’t show up. Now what?

That’s deliverability or recipient-side filtering. Verify SPF/DKIM/DMARC alignment, check relay event logs for bounces/deferrals, and test sending to multiple providers. Also check spam/quarantine folders.

4) Can I just use my Gmail or Microsoft 365 account credentials?

You can, but it’s fragile. Providers change auth rules, enforce MFA, and limit throughput. Prefer a dedicated relay credential or provider-supported app password/OAuth flow with a service account.

5) What “From” address should WooCommerce use?

Use an address on your domain that you authenticate (e.g., orders@example.com). Force it in your SMTP plugin so templates and extensions don’t override it into misalignment.

6) Why do contact form emails often fail only for certain senders?

If the form sets From to the visitor, DMARC policies of the visitor’s domain can cause rejection or spam placement. Many large domains publish strict DMARC specifically to stop this kind of spoofing.

7) Do I need DKIM if SPF is correct?

Yes if you want consistent delivery. SPF can break with forwarding, and it doesn’t sign content. DKIM adds integrity and often improves trust. DMARC works best when both are present.

8) What’s the quickest way to tell if it’s a network block?

Run nc -vz relay 587 and openssl s_client -starttls smtp from the WordPress host. Timeouts and “no route” messages beat guessing.

9) Is running my own Postfix for outbound mail ever a good idea?

Sometimes: if you have a mail team, stable IP space, reverse DNS control, and the appetite to manage reputation and abuse. For most WordPress sites, relaying through a provider is cheaper than your time.

10) How do I stop emails from going to spam without changing content?

Start with authentication alignment (SPF/DKIM/DMARC), then sending consistency (steady volumes), then reputation (use a trusted relay). Content tweaks help, but identity is the foundation.

Next steps you can do today

If WordPress email is failing, don’t argue with it. Instrument it and give it a real transport.

  1. Move off PHP mail() unless you’re intentionally operating a local MTA with logs and a relay path.
  2. Configure authenticated SMTP to a dedicated relay on 587/465 and test connectivity from the host.
  3. Publish and verify SPF/DKIM/DMARC and make sure the WordPress From address aligns with what your relay authenticates.
  4. Fix form plugins so visitor email goes into Reply-To, not From.
  5. Keep logs (plugin + relay) and use them to distinguish transport failures from deliverability issues.

Do those, and “WordPress not sending email” stops being a recurring mystery and becomes a boring, monitorable subsystem. Boring is good. Boring is how you sleep.

← Previous
Why Drivers Will Become Part of the Game Even More
Next →
3dfx Voodoo: the card that made 3D mainstream

Leave a comment