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.
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:
- CF7 calls wp_mail.
- PHPMailer calls PHP mail.
- PHP mail hands the message to the host's local MTA (often Exim or Postfix).
- The MTA stamps an envelope sender like
www-data@srv42.shared-host.tldand 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
- Install the SMTP plugin. Choose the relay. Paste the API key or SMTP credentials.
- 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.
- Verify the domain inside the relay. This usually means publishing a TXT record (SPF or domain verification) and two or three DKIM CNAMEs.
- 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, anddmarc=passall show in the Authentication-Results header. - 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
TXTat 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 atv=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com, read the aggregate reports for two weeks, then tighten top=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:
- 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).
- Paste the raw message into a placement tester that sends across 20+ provider seeds and reports folder, authentication, DNSBLs, and spam engine scores.
- Fix whatever the test surfaces — broken DKIM, missing List-Unsubscribe, a listed tracking subdomain.
- Re-test. Commit the DNS and plugin config to your infra repo so the next environment does not regress.
A native wp-admin plugin is in private beta — run placement tests from your dashboard and alert on drops.