Your contact form says “Thanks for your message.” Your client never got it. Three weeks later they call, furious, because they’ve been ignored. Nobody was ignored — the email just quietly vanished somewhere between your WordPress server and their Gmail spam folder.
If you’ve been blaming WP Mail SMTP, or telling clients “it’s probably in spam,” you’re treating a symptom. WordPress email delivery is a three-layer problem, and almost every article you’ll read on the topic only fixes the first layer. This is how to actually get your forms reliably delivered in 2026, at every layer, with the tests to prove it.
The Three Layers of Failure
Before you touch anything, understand the failure surface. Email delivery has three independent layers that can each kill your messages:
Layer 1 — WordPress can’t reliably send. wp_mail() uses PHPMailer, which by default hands the email to the server’s sendmail binary. On managed hosts, that binary is either disabled, rate-limited, or routes through a shared IP with a terrible reputation. This is the layer most articles cover.
Layer 2 — DNS can’t prove the email is authentic. Even if WordPress successfully hands the email to a mail server, the receiving server looks up your domain’s SPF, DKIM, and DMARC records. If any of them are missing or misconfigured, Gmail will either dump the message in spam or drop it at the envelope. As of February 2024, Gmail and Yahoo now enforce this. Before 2024 they were lenient. Now they’re not.
Layer 3 — Your sender reputation is bad. Even if the DNS is perfect, Gmail and Microsoft look at your sending IP’s history. If another site on that IP got a spam complaint last week, yours gets punished. If you send a newsletter blast from the same domain you send order receipts from, a 2% unsubscribe rate on the newsletter damages the order receipts.
Every “email delivery fix” article you’ve ever read covers Layer 1 and maybe Layer 2. Layer 3 is where most sites actually fail in 2026, and we’ll spend the most time there.
Layer 1: Prove WordPress Is Actually Sending
Start here. You cannot fix a delivery problem until you know whether the email is leaving the building.
The wp_mail() function is a wrapper around PHPMailer. When it succeeds, it returns true. When it fails, it returns false. Most plugins — including every major form plugin — check this return value and show a generic error if it’s false, and a success message if it’s true. “Success” here means exactly one thing: PHPMailer handed the message to the configured transport without throwing. It does not mean the message was received. It does not mean the message was accepted by a mail server. It definitely does not mean it was delivered.
First, log every attempt. Drop this into a must-use plugin (wp-content/mu-plugins/debug-mail.php):
<?php
/**
* Log every wp_mail() call for debugging deliverability.
* Remove once diagnosis is complete — this is not for production long-term.
*/
add_filter('wp_mail', function(array $args): array {
$log = sprintf(
"[%s] TO: %s | SUBJECT: %s | FROM_HEADERS: %s\n",
gmdate('c'),
is_array($args['to']) ? implode(',', $args['to']) : $args['to'],
$args['subject'] ?? '(no subject)',
is_array($args['headers'] ?? null) ? implode(' | ', $args['headers']) : (string) ($args['headers'] ?? '')
);
error_log($log, 3, WP_CONTENT_DIR . '/mail-debug.log');
return $args;
});
add_action('wp_mail_failed', function(WP_Error $error): void {
error_log(
sprintf("[%s] FAILED: %s\n", gmdate('c'), $error->get_error_message()),
3,
WP_CONTENT_DIR . '/mail-debug.log'
);
});
Now trigger your form. Open wp-content/mail-debug.log. You should see the attempt. If you don’t, the problem isn’t delivery — it’s that your form plugin isn’t calling wp_mail() at all. WPForms and Gravity Forms both call it. Fluent Forms calls it. If your plugin doesn’t, check whether it has its own separate SMTP settings (some do) and use those instead.
If you see the attempt logged but wp_mail_failed never fires, WordPress thinks the email was sent. Now the question is whether your transport actually delivered it.
Route WordPress through a real SMTP transport
The default PHPMailer transport — PHP’s mail() function shelled out to the server’s sendmail — is the root of most Layer 1 failures. It’s unauthenticated, it sends from your server’s IP (often a shared IP with a bad reputation), and it gives you zero visibility.
Replace it with a real SMTP transport. The two options are:
- An SMTP plugin like FluentSMTP, WP Mail SMTP, or Post SMTP. These wrap PHPMailer and let you point it at a mail API or SMTP endpoint.
- A filter-based custom integration. Lighter, more control, no plugin bloat.
For a single site, the plugin approach is fine. FluentSMTP is the cleanest in 2026 — free, actively maintained, supports native APIs for most providers rather than dirty SMTP, and has real logging. Skip WP Mail SMTP’s paid features; they’re not worth the overhead.
For agencies managing multiple sites, use the filter approach and bake it into a must-use plugin you can deploy across your portfolio:
<?php
/**
* wp-content/mu-plugins/smtp-config.php
* Route wp_mail through Postmark API — no plugin dependency.
*/
add_action('phpmailer_init', function(PHPMailer\PHPMailer\PHPMailer $mail): void {
if (!defined('POSTMARK_SMTP_TOKEN')) {
return;
}
$mail->isSMTP();
$mail->Host = 'smtp.postmarkapp.com';
$mail->SMTPAuth = true;
$mail->Username = POSTMARK_SMTP_TOKEN;
$mail->Password = POSTMARK_SMTP_TOKEN;
$mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->From = 'hello@mail.yoursite.com';
$mail->FromName = 'Your Site';
});
add_filter('wp_mail_from', fn() => 'hello@mail.yoursite.com');
add_filter('wp_mail_from_name', fn() => 'Your Site');
Then in wp-config.php:
define('POSTMARK_SMTP_TOKEN', 'your-token-here');
This is sixty lines of code that replaces a 3MB plugin. On every site you manage.
Fix the “From” address trap
This is the single most common form misconfiguration we see. It’s responsible for more “my emails go to spam” tickets than any other single issue.
When someone fills out your contact form, the naïve setup is: “From: the user who submitted the form.” It feels natural — when the email hits your inbox, you can reply directly. But here’s what actually happens:
- Your server at yoursite.com sends an email
- With the From: header set to randomuser@gmail.com
- Gmail’s receiving server checks: does yoursite.com have permission to send as gmail.com?
- Answer: absolutely not
- Message: rejected or spam-foldered
This is forging the sender. You are literally spoofing someone else’s domain. SPF, DKIM, and DMARC exist specifically to stop this. You cannot work around it.
The correct pattern: From: is always your own domain. Put the submitter’s email in the Reply-To: header. Every major form plugin supports this — you just have to configure it correctly:
From: hello@mail.yoursite.com
Reply-To: {user_email}
Gmail will hit reply for you and it’ll go to the user. The message itself comes from you, which is honest and deliverable.
Layer 2: Prove Your Domain Is Allowed to Send
Once WordPress is handing emails to a real mail provider, the next layer is DNS authentication. Gmail and Yahoo have required this since February 2024 for bulk senders and they’re steadily tightening the thresholds. By mid-2025, even a few hundred messages a day without proper authentication started getting filtered. By 2026, it’s effectively mandatory for everyone.
You need three DNS records. Here’s what each one actually does and how to configure it correctly.
SPF — who’s allowed to send from your domain
SPF is a TXT record on your root domain (or sending subdomain) that lists the mail servers allowed to send on your behalf.
v=spf1 include:spf.mtasv.net include:_spf.google.com -all
This says: “Mail from this domain is valid if it comes from Postmark (spf.mtasv.net) or Google Workspace (_spf.google.com), and nothing else (-all).”
The gotchas:
- You can only have one SPF record per domain. If you add a second, every SPF lookup fails. Merge them.
- You can only have 10 DNS lookups inside a single SPF record. Every include: counts as a lookup. If you have five mail services, you hit the limit fast. Solution: flatten your SPF with a tool like dmarcly.com/tools/spf-record-flattener or switch providers to ones that use a single include.
- Use -all (hard fail), not ~all (soft fail). Soft fail is a fossil from 2008. In 2026, receivers treat soft fail like hard fail anyway, but hard fail is clearer to debug.
DKIM — cryptographic signature of the email content
DKIM adds a signature header to outgoing email, and a public key in your DNS. The receiving server grabs the public key, verifies the signature, and confirms the email wasn’t tampered with in transit and did come from your domain.
Your SMTP provider generates the DKIM keys. You paste the record into DNS. The record looks something like:
postmark._domainkey.yoursite.com. TXT "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
The selector (postmark in this example) lets you have multiple DKIM keys for different services — important if you use one service for transactional and another for marketing, which you should (more on that in a minute).
Common DKIM mistakes:
- Copying the record with line breaks intact. DNS providers handle long TXT records differently; some auto-chunk, some don’t. Verify with dig TXT postmark._domainkey.yoursite.com after adding.
- Only setting up DKIM for one provider when you use two. Every provider needs its own selector record.
- Forgetting to enable DKIM signing in the provider’s dashboard after adding the DNS record. DNS is half of the setup.
DMARC — what to do when SPF or DKIM fails
DMARC is the policy that tells receivers what to do with messages that fail SPF and DKIM. It’s also how you find out when your domain is being spoofed.
Start with a monitoring policy:
_dmarc.yoursite.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@yoursite.com; fo=1; adkim=s; aspf=s"
Breakdown:
- p=none — don’t block anything yet, just send reports
- rua=mailto:dmarc@yoursite.com — send aggregate reports here
- fo=1 — send forensic reports for any failure
- adkim=s; aspf=s — strict alignment (the signing domain must match the From: domain exactly, not just be a subdomain)
Run p=none for two weeks. You’ll get daily aggregate XML reports from Google, Yahoo, Microsoft, and others. Parse them with a free tool like Postmark’s DMARC monitor or DMARCian’s free tier. You’ll immediately see every legitimate source sending as your domain (including ones you forgot about — marketing tools, recruiting platforms, random SaaS). Make sure they’re all covered by SPF/DKIM.
Once clean, move to p=quarantine for two weeks. Then p=reject. This is the actual target state. Anything less leaves you vulnerable.
Layer 3: Prove You’re a Trustworthy Sender
Here’s where it gets interesting, and where every other article stops.
You can have perfect SPF, DKIM, and DMARC and still get filtered. Gmail doesn’t just check authentication — it checks reputation. Your sending IP, your sending domain, and your content all have a reputation score that’s computed over time. A high score gets you the inbox. A low score gets you spam. A terrible score gets you blocked at the envelope.
Reputation is owned by the sender, not the provider. Moving from SendGrid to Postmark doesn’t reset your domain’s reputation. What matters is how recipients interact with your mail, over time, consistently.
Separate transactional and marketing streams
This is the single biggest reputation strategy, and you should do it even if you only send 10 emails a day.
Do not send order confirmations from the same domain or IP that sends newsletters. Ever.
Why: marketing email has a 1-3% unsubscribe rate and a 0.3-1% spam complaint rate even when you’re doing everything right. Transactional email has a 0.001% complaint rate. If you mix them, Gmail’s reputation model averages them out, and your order confirmations start landing in spam.
The fix: use subdomains.
- hello@mail.yoursite.com — transactional only (contact forms, order confirmations, password resets)
- news@news.yoursite.com — marketing only (newsletters, promos, abandoned cart)
Each subdomain has its own SPF, DKIM, and DMARC. Each has its own sending provider (or at least its own dedicated IP pool on the same provider). Reputation is isolated. If marketing messes up, transactional is fine.
This is also why competing advice to “just use your Google Workspace SMTP for WordPress email” is bad in 2026. Your Google Workspace account has a rate limit of 2000 messages/day and no good way to track deliverability. It’s fine for three emails a month. It’s not a solution.
Pick the right provider — and stop using SendGrid free
Here’s the opinionated part.
For transactional WordPress email in 2026, use Postmark. It’s not free, but it’s the cheapest thing in your stack that has the highest direct impact on revenue. Postmark has a dedicated transactional IP pool, ruthless anti-abuse policies that keep their IP reputation clean, and 45-day-average deliverability above 99%. It’s the best bucket on the internet because they refuse to let marketers use it.
Do not use the SendGrid free tier. SendGrid’s free IP pool is shared with every spammer who signed up for a free account, and the resulting reputation is what you’d expect. Even paid SendGrid is a mixed bag unless you pay for dedicated IPs, at which point it’s more expensive than Postmark anyway.
Mailgun is fine. Brevo (formerly Sendinblue) is fine for low-volume. Amazon SES is cheap but has no reputation management — you get the rep of whatever random pool you end up in.
For marketing, use a different provider entirely. Anything that isn’t your transactional provider. Klaviyo, Mailchimp, ConvertKit — whatever fits your workflow. Keep the streams separate.
Warm up new sending domains
If you just set up mail.yoursite.com, don’t send 5,000 emails from it tomorrow. Gmail will flag the sudden volume as suspicious and throttle or block you.
Warm-up schedule:
- Day 1-3: 50 emails/day
- Day 4-7: 200 emails/day
- Day 8-14: 500 emails/day
- Day 15+: normal volume
During warm-up, prioritise sending to your most engaged contacts — people who will definitely open the email. Opens and positive signals build reputation fastest. Sending warm-up volume to cold lists actively damages reputation.
This is only relevant if you’re switching providers or launching a new sending domain. Existing domains don’t need warming.
The Gmail/Yahoo Rules That Changed Everything
February 2024. Google and Yahoo jointly announced new sender requirements. They applied initially to bulk senders (5000+ messages/day), then rolled down to smaller senders over 2024-2025. As of 2026, they’re enforced broadly enough that you should assume all the rules apply to you.
Requirement 1: SPF and DKIM alignment with the From: header. Already covered above. Both must pass, both must align.
Requirement 2: DMARC with at least p=none. The policy value doesn’t matter for compliance (yet), but the record must exist.
Requirement 3: One-click unsubscribe via the List-Unsubscribe header (RFC 8058). Officially only required for marketing, but transactional mail with good List-Unsubscribe-Post support is treated better. Add it:
add_filter('wp_mail', function(array $args): array {
if (!isset($args['headers']) || !is_array($args['headers'])) {
$args['headers'] = $args['headers'] ? [$args['headers']] : [];
}
$unsub_url = 'https://yoursite.com/unsubscribe?token=' . wp_generate_password(20, false);
$args['headers'][] = 'List-Unsubscribe: <mailto:unsubscribe@yoursite.com>, <' . $unsub_url . '>';
$args['headers'][] = 'List-Unsubscribe-Post: List-Unsubscribe=One-Click';
return $args;
});
Yes, even transactional. The filter looks good. Just make sure the URL actually does something — having the header and a broken endpoint is worse than not having it.
Requirement 4: Spam complaint rate below 0.3%. This is measured in Google Postmaster Tools. If you cross 0.3%, Gmail starts filtering you. Cross 0.5% and you’re blocked. If you don’t have Postmaster Tools set up on your domain, you’re flying blind and you won’t know you have a problem until it’s already bad.
Testing: Three Tools, Run in Order
You cannot trust “it looked fine in my inbox” as a test. Here’s the testing stack.
mail-tester.com — free, unlimited, instant feedback. Go to mail-tester.com, copy the random email address it gives you, trigger a form submission to that address, click “Then check your score.” You’ll get a detailed breakdown of SPF, DKIM, DMARC, content, and headers with an overall score out of 10. Target: 10/10. Anything below 9/10 has a fixable issue.
GlockApps — paid but worth it for real deliverability testing across providers. It sends a test email to seed addresses at Gmail, Outlook, Yahoo, AOL, ProtonMail, and dozens more, then reports where each one landed (inbox, spam, promotions tab, blocked). mail-tester tells you your score. GlockApps tells you where you actually land.
Google Postmaster Tools — free, but only useful once you’re sending real volume to Gmail. Shows your domain reputation, IP reputation, spam rate, and authentication pass rate over time. Essential for diagnosing slow-burn reputation problems. Set it up on every domain you send from before you need it.
Common Edge Cases That Break Everything
WooCommerce emails
WooCommerce sends 8-12 different emails per order (new order, processing, completed, failed, customer invoice, customer note, subscription renewal, etc). Every one goes through wp_mail(), so once WordPress is configured correctly, WooCommerce is mostly configured. Mostly.
The gotcha: WooCommerce sometimes builds the message before phpmailer_init fires, and plugins that override wp_mail instead of hooking phpmailer_init can miss these. If you’re seeing “new order” emails not send but contact form emails working, that’s your bug. Switch to the phpmailer_init hook.
Also: WooCommerce From: address is configurable in WooCommerce → Settings → Emails. Make sure it matches your SPF/DKIM domain. We’ve seen sites where the global wp_mail_from filter is set correctly but WooCommerce is still sending from the old wordpress@yoursite.com address.
BuddyPress, bbPress, LMS plugins
These plugins send activity emails in bulk — forum replies, new comments, course completions. Bulk sending has two risks: (1) you blow past your provider’s rate limits, causing some messages to bounce; (2) recipients mark them as spam because they don’t remember signing up for notifications. Both kill reputation.
Solutions: digest mode (bundle notifications into a daily email), double opt-in for any email-based feature, and always include an easy one-click unsubscribe.
Multisite
On WordPress multisite, each subsite can override wp_mail settings independently. If you configure SMTP at the network level via a must-use plugin, great. If you let each site install its own SMTP plugin, you’ll have chaos — some sites on SendGrid, some on Google, some on default, all with different reputation characteristics, and no central way to debug a specific one. Always configure SMTP at the network level for multisite.
The Monitoring Routine
Set up these alerts once and forget them:
- Weekly: Run a mail-tester.com test and log the score. If it drops below 9/10, investigate.
- Weekly: Check Google Postmaster Tools for spam rate. If above 0.1%, investigate.
- Monthly: Review DMARC aggregate reports for unauthorised sources.
- Monthly: Send a test form from an incognito browser and verify receipt in Gmail, Outlook, and a non-work address.
- Immediately: Set up a bounce webhook from your provider. When bounces spike, know it the same day.
Nobody catches the slow reputation decay until it’s too late. These five checks catch it early.
What to Fix First
If you’re reading this because your contact forms aren’t being received, here’s the actual order of operations:
- Install FluentSMTP, point it at Postmark (or Mailgun, or Brevo — just not default).
- Fix the From: address to be your own domain. Set Reply-To: to the submitter.
- Add SPF, DKIM, and DMARC (p=none) records. Verify with dig and mail-tester.
- Create a mail subdomain for transactional and move sending to it. Update DNS.
- Set up Google Postmaster Tools for the new subdomain.
- Test with mail-tester.com. Target 10/10.
- Move DMARC to p=quarantine, then p=reject over the next month.
- If sending marketing email, move it to a different subdomain and a different provider.
Steps 1-3 solve 80% of cases. Steps 4-5 solve the next 15%. Steps 6-8 are for when you need to solve the last 5% — which is usually the 5% that costs you money.
If form delivery is a recurring problem and the stakes are high — WooCommerce order confirmations, client leads, payment failures — this is the kind of work we handle constantly. Our WordPress Plugin Customization service covers custom SMTP integrations, form reliability work, and deliverability audits, and it starts at $399. A single missed lead is usually worth more than the fix.
Email delivery isn’t hard. It’s just layered, and almost everyone only fixes one layer. Fix all three and forms stop vanishing.