WordPress forms7 min read

Contact Form 7 emails going to spam: the fix nobody talks about

Every CF7 tutorial tells you to tweak the subject line or the from address. The real problem is one layer lower — wp_mail is calling PHP mail, and PHP mail produces messages that modern filters reject on sight.

Contact Form 7 is still the most installed forms plugin on WordPress. It is also, year after year, the one generating the most "my form emails are going to spam" support threads. The advice in those threads is almost always wrong: change the sender, add a reply-to, tweak the subject. None of that fixes the real issue.

The real issue is the sending path. CF7 calls wp_mail(). On a default WordPress install, wp_mail() wraps PHP's built-in mail() function. That path produces messages that fail SPF, fail DKIM, fail DMARC, and get binned by Gmail, Outlook, and every serious filter in between. Until you fix the path, no amount of copy tuning will get your form mail into the inbox.

The quick answer

Install an SMTP plugin, connect it to a real authenticated relay (your mailbox provider, Amazon SES, Brevo, Postmark, or Resend), publish SPF and DKIM for the sending domain, and seed-test the form. Everything else is secondary.

What wp_mail actually does

When a visitor submits a CF7 form, CF7 builds a message and calls wp_mail(). Without an SMTP plugin, WordPress hands that message to PHPMailer configured to use the local sendmail or PHP mail() transport. On typical shared hosting the chain looks like this:

  1. CF7 calls wp_mail.
  2. PHPMailer calls PHP mail.
  3. PHP mail hands the message to the host's local MTA (often Exim or Postfix).
  4. The MTA stamps an envelope sender like www-data@srv42.shared-host.tld and relays the message from the host's IP.

That envelope sender does not match the From: header your form is using (hello@yourdomain.com). The host's shared IP is not listed in your SPF record. And there is no DKIM signature anywhere because no one has keys for your domain on that relay. Result: SPF fails, DKIM is absent, DMARC fails alignment, and Gmail's filter moves the message to Spam — or rejects it outright if you have p=quarantine or p=reject on DMARC.

The minimum fix: SMTP plugin plus a real relay

You need to replace the default transport with an authenticated SMTP connection to a mailbox provider or transactional relay that is allowed to send for your domain. Three sane choices depending on volume:

  • Your mailbox provider (Google Workspace, Microsoft 365, Fastmail, Zoho). Good for under 100 form submissions per day. Submit as the logged-in mailbox; the provider will sign with its own DKIM key for your domain.
  • Transactional relay (Postmark, Brevo, Amazon SES, Resend, Mailgun). Good for anywhere from a handful to millions of messages. You verify the domain, publish the relay's DKIM CNAMEs, and authenticate via API key.
  • Self-hosted MTA with DKIM (Postfix plus OpenDKIM). Fine if you already run mail infrastructure. Not worth setting up for a contact form.

Pick an SMTP plugin — WP Mail SMTP, FluentSMTP, and Post SMTP are the three dominant options. All three override wp_mail(), let you configure the relay, and log every message WordPress tries to send.

Plugin setup in five steps

  1. Install the SMTP plugin. Choose the relay. Paste the API key or SMTP credentials.
  2. Set the From: address to a mailbox on a domain you own and control DNS for. Do not use the visitor's email as the From: — use it as Reply-To:. More on this below.
  3. Verify the domain inside the relay. This usually means publishing a TXT record (SPF or domain verification) and two or three DKIM CNAMEs.
  4. Send a test from the plugin's built-in tester to a Gmail address and view the raw source. Check that spf=pass, dkim=pass, and dmarc=pass all show in the Authentication-Results header.
  5. Submit the actual CF7 form once. Confirm the message lands in Inbox at the seed mailbox and the headers look the same.

The From: address trap

The single most common mistake in CF7 configuration is putting the visitor's email address in the From: field. The reasoning feels intuitive: "I want to reply to them directly from my inbox". The consequence is that every form submission has a From: header claiming to be from a domain you do not control, which fails DMARC alignment on the sender side and either gets binned or rejected.

The correct pattern:

From:     "Website Form" <forms@yourdomain.com>
Reply-To: [your-name] <[your-email]>
To:       hello@yourdomain.com
Subject:  Contact form: [your-subject]

From: is always a mailbox on your own domain. Reply-To: carries the visitor's details — when you hit Reply in your mailbox, it routes to them. This is the only configuration that survives DMARC with a modern policy.

DNS records to publish

Publishing authentication records is not optional in 2026. Both Gmail and Yahoo reject unauthenticated bulk mail, and Microsoft applies increasingly hostile scoring to unauthenticated transactional traffic. Minimum records on the sending domain:

  • SPF: a single TXT at the apex. Include your relay's mechanism. Example for SES: v=spf1 include:amazonses.com -all.
  • DKIM: the two or three CNAMEs your relay provides. They point at the relay's key host so the relay can rotate keys without your help.
  • DMARC: a TXT at _dmarc.yourdomain.com. Start at v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com, read the aggregate reports for two weeks, then tighten to p=quarantine.

Verify with a free DNS checker, then send a test and read the raw headers to confirm all three pass.

Seed-testing the final pipeline

A test to one Gmail inbox tells you almost nothing. Gmail's filter is tuned differently for Promotions versus Primary versus Updates, and Outlook, Yahoo, iCloud, and the European ISPs all score you independently. The only way to see what real recipients see is to drop the same message into a fanned-out seed list and read the verdicts.

Workflow for CF7:

  1. Grab the rendered notification email from your SMTP plugin's log (both WP Mail SMTP and FluentSMTP keep a full copy of the most recent message).
  2. Paste the raw message into a placement tester that sends across 20+ provider seeds and reports folder, authentication, DNSBLs, and spam engine scores.
  3. Fix whatever the test surfaces — broken DKIM, missing List-Unsubscribe, a listed tracking subdomain.
  4. Re-test. Commit the DNS and plugin config to your infra repo so the next environment does not regress.
WordPress integration in beta

A native wp-admin plugin is in private beta — run placement tests from your dashboard and alert on drops.

→ Join the beta waitlist

Frequently asked questions

Will changing the subject line fix CF7 spam?

No. Subject-line changes help only if you are fighting a content-scoring filter at a low-margin. If SPF, DKIM, or DMARC are failing, the message will be dropped into Spam before the filter ever reads the subject.

Do I still need SPF and DKIM if the relay signs for me?

You always need SPF for your sending domain. DKIM is signed by the relay, but the relay needs CNAMEs under your domain to publish the public key — so you publish DNS either way. Without both, DMARC cannot align.

What is the difference between WP Mail SMTP and FluentSMTP for this?

Functionally they overlap. WP Mail SMTP has a slicker onboarding and a paid tier with extra integrations. FluentSMTP is free, open source, and includes all providers in the free version. Either will fix the CF7 spam problem.

Can I keep the visitor email as From: if I have DMARC p=none?

Technically yes, but you are relying on weak policy enforcement. Gmail and Yahoo have been tightening their own behavior regardless of your DMARC policy, and many corporate receivers reject outright. Always use your own domain in From: and the visitor in Reply-To:.
Related reading

Check your deliverability across 20+ providers

Gmail, Outlook, Yahoo, Mail.ru, Yandex, GMX, ProtonMail and more. Real inbox screenshots, SPF/DKIM/DMARC, spam engine verdicts. Free, no signup.

Run Free Test →

Unlimited tests · 20+ seed mailboxes · Live results · No account required