A visitor fills in your contact form. The page says “Thank you.” They believe the message is on its way. Nobody at your company ever sees it—no bounce, no error, no entry in any log. The form is not broken. The mail path is.
In the audits I run, missing form mail shows up in three shapes. Nothing arrives at all, and the owner only finds out when a customer calls to ask why they were ignored. Or mail lands at Gmail but never at Outlook or GMX, so the problem looks like “one provider being difficult.” Or it is intermittent—four out of five inquiries arrive, the fifth does not, and everyone blames “the internet being weird.”
Every published answer to this question says “install an SMTP plugin.” That is sometimes the fix and almost never the diagnosis. Below is the order I work through, cheapest check first, plus a test protocol you can run in 15 minutes and a monitoring setup so the next failure is loud.
Why the failure is silent
WordPress sends mail through wp_mail(), which by default hands the message to PHP’s mail(), which hands it to whatever mail daemon happens to run on the web server. That handoff is where the success signal comes from. mail() returns true once the local daemon accepts the message—not when a recipient’s server accepts it, and certainly not when a human reads it.
If the message is rejected two hops later, the bounce goes to the envelope sender, which on a default setup is something like www-data@srv1234.hoster.tld. That mailbox either does not exist or is read by nobody. So the rejection is real, documented, and invisible to you. The plugin’s log, if it has one, still says “sent.”
That is the whole reason this problem survives for months. Every other failure on a website announces itself. This one does not.
Cause 1: the From-header lie
Most form plugins let you set the notification’s From address, and the most intuitive setting is the worst one: put the visitor’s address there. Now your web server sends a message claiming to be from customer@gmail.com.
The receiving server asks the obvious question—is this machine authorized to send as gmail.com?—and the answer is no. When both SPF and DKIM fail for the domain in the From line, Gmail answers with a 550-5.7.26 rejection, which SendLayer’s support write-up describes plainly as an unauthenticated-mail refusal rather than a judgment about your content. Rewriting the message text will not help. The problem is identity, not vocabulary.
The correct pattern is boring and it works:
- From: an address on a domain you control and are authorized to send from—
website@example.com, not the visitor’s address. - Reply-To: the visitor’s address, so hitting Reply still goes straight to the lead.
- Subject and body: carry the visitor’s name and address as text, so your CRM and your search still find them.
You lose nothing operationally. You stop asking Gmail, Microsoft and GMX to accept a forgery.
Cause 2: the envelope sender nobody looks at
Fixing the visible From header fixes half the identity problem. SPF does not read that header at all. SPF is evaluated against the envelope sender—the Return-Path, the address given during the SMTP conversation—which MxToolbox describes as the address receiving servers use both for bounces and for the SPF lookup. It can be, and on default hosting usually is, completely different from what the recipient sees.
On shared hosting that envelope is typically the local system user at the hoster’s server hostname. SPF then passes beautifully—for the hoster’s domain. For yours it is not merely neutral, it is unaligned, and alignment is the thing DMARC checks. As Suped puts it, SPF authenticates only the envelope domain; authenticating the domain a human actually sees requires DMARC layered on top.
A healthy header block on a received test message looks roughly like this, with the same domain on the left of every line:
Authentication-Results: mx.google.com;
spf=pass smtp.mailfrom=bounce@example.com;
dkim=pass header.i=@example.com;
dmarc=pass (p=NONE sp=NONE) header.from=example.com
If smtp.mailfrom ends in your hoster’s domain and header.from ends in yours, you have found your misalignment. That single line explains more missing mail than any plugin setting.
Cause 3: no DKIM anywhere on the sending path
Mail sent straight out of a web server is normally unsigned. There is no DKIM key, no selector, nothing in the headers for a receiver to verify. Your mailbox provider signs the mail you send from your laptop; the website is a separate sending system and inherits none of that.
With no aligned SPF and no DKIM, DMARC cannot pass—it needs at least one of the two to pass and align. And here is the part that surprises people: if your domain publishes a DMARC policy of p=quarantine or p=reject, you have asked the world to junk or refuse mail that fails, and your own contact form is exactly such mail. The policy is doing its job. Your form is the unauthorized sender.
The fix is never “generate a key yourself.” You take the DKIM key your mail provider or relay generates, publish the record they give you at your DNS provider, and switch signing on in their dashboard. RFC 8301 sets 1024 bits as the floor and recommends 2048; some sending services only offer 1024, which is a vendor limit rather than something you misconfigured.
Cause 4: the host blocks outbound mail on purpose
You can configure everything correctly and still send nothing, because the platform will not let the connection out. WP Engine states it directly in its own documentation: its infrastructure providers do not allow mail over port 25, so any SMTP solution has to use an alternate port or a provider API. Several other managed and shared hosts apply comparable restrictions—check your own host’s docs rather than assuming.
The symptom is distinctive. The plugin says “configured.” The test dialog spins and then times out, or reports a connection error with no detail. Confirming it takes about two minutes:
- Search your host’s support docs for “outbound mail,” “port 25” or “SMTP.” Most hosts that block it say so on one page.
- If you have SSH access, open a read-only connection test to your relay’s submission port:
openssl s_client -starttls smtp -connect smtp.example.net:587. A banner means the path is open; a hang or refusal means it is not. - Switch the relay to its API transport instead of SMTP and re-test. If mail suddenly flows, the port was the problem.
Port 587 with credentials is open on most platforms that block 25. An HTTPS API is open on effectively all of them, which is why API transports have become the default recommendation on managed hosting.
Cause 5: it is not always your fault
Sometimes your authentication is clean and the mail still crawls. Receiving providers have incidents. Exchange Online ran one tracked as EX1464935 starting August 31, 2026, with delays and failures sending and receiving mail plus authentication errors, which Microsoft traced to a common failure pattern in authentication and protocol connectivity and reported mitigated by September 3. A separate incident, EX1467029, opened September 4, 2026, specifically delaying mail to and from external domains, with Microsoft noting that anti-spam protections appeared to compound the problem for some accounts.
Add tenant-level transport rules, aggressive quarantine settings, and plain old junk folders, and a meaningful share of “your form is broken” tickets are actually “the recipient’s mail system did something.”
You can tell the two apart from one message. If you can open a received copy and the Authentication-Results line shows SPF, DKIM and DMARC passing on your domain, the sending side is doing its job and you should look at the recipient’s filters, rules and quarantine. If those checks fail, stop looking at the recipient—the problem is upstream in your own configuration, and no provider-side setting will paper over it.
The architecture that actually works
Strip away the plugin marketing and there is one design that holds up:
- An authenticated relay instead of
mail(). SMTP submission with credentials, or the relay’s HTTPS API. The point is not speed—it is that a relay gives you a real accept-or-reject answer and a log you can read. - From on a domain you control, with one address reserved for the website, so you can tell at a glance whether a message came from the form or from a person.
- Reply-To carrying the visitor. Replying still reaches the lead in one click.
- An envelope sender aligned with the From domain, or at minimum a DKIM signature that is aligned, so DMARC has something to pass on.
- One sending identity per system. Website, newsletter tool, store, invoicing and CRM each get their own identity, so when something fails you know which system failed.
That last point is the one people skip and regret. If four systems share one identity, a deliverability problem is a guessing game. If each has its own, the DMARC reports tell you the answer.
The DNS half
Before you touch anything, look at what is already published. These are read-only lookups:
dig +short TXT example.com
dig +short TXT _dmarc.example.com
dig +short TXT selector1._domainkey.example.com
# Windows
nslookup -type=TXT example.com
nslookup -type=TXT _dmarc.example.com
Copy the current value of every record before you change anything. Then work one record at a time.
SPF. A domain has exactly one v=spf1 record. If your relay needs to be authorized, you edit the existing record to add its include—never publish a second one, because two SPF records is itself a failure. Watch the lookup budget: RFC 7208 caps the DNS lookups a single SPF evaluation may trigger at ten, counting everything inside nested includes, and going over returns a permerror that receivers treat as failure. It is invisible until you test for it. And do not move the record’s ending to -all until you have inventoried every system that sends as your domain.
DKIM. Publish the record your provider hands you, at the selector they specify, then confirm in their dashboard that signing is actually switched on for the domain—published-but-not-selected is a common half-finished state. Rotating a key later means adding the new selector, switching signing over, and removing the old record afterwards, never in one step.
DMARC. One _dmarc record, starting at p=none with a rua= address that a human actually reads. At p=none you are asking receivers for no specific action on mail that fails, while collecting reports on who is sending as your domain and whether they align—Sendmarc describes this monitoring phase as exactly what the data-collection stage is for, and equally notes that staying there forever leaves spoofing unaddressed.
The 15-minute test protocol
Run this on the live site, not a staging copy—staging often has a different sending path.
- Submit the real contact form three times, once each to a Gmail address, an Outlook or Microsoft 365 address, and a GMX or other European freemail address.
- Open the received message at Gmail via “Show original,” at Outlook via message headers, and read the
Authentication-Resultsline. - Confirm three passes:
spf=pass,dkim=pass,dmarc=pass. - Confirm alignment: the domain in
header.frommatches the domain insmtp.mailfromor inheader.ifor DKIM. Two passes on the hoster’s domain are not a pass for you. - Check where the message landed. Inbox, Promotions, Junk and quarantine are four different outcomes and only one of them is fine.
- Send one submission to a mail-tester.com address and read the breakdown. It scores SPF, DKIM, DMARC, blocklist status and formatting from 0 to 10—useful as guidance, and its own documentation is clear that the score is not a delivery guarantee.
- Enable a mail-logging plugin and submit once more. Verify the log records a real handoff to your relay, with a message ID, not just a local “sent” flag.
- If any address never arrives, look at the relay’s own log next. A relay that accepted and then got a rejection will tell you the exact reason the web server could not.
Where an address is missing entirely and the relay has no record of it, you are back at Cause 4: nothing left the building.
Monitoring, so the next failure is loud
Getting mail flowing today is the easy half. Keeping the failure from going silent again takes four small habits.
- Mail logging with retention. A log that keeps 30 to 90 days turns “I think someone contacted us in August” into a lookup.
- DMARC aggregate reports, read monthly. Ten minutes with the reports is how you notice a new sender or a newly failing one before a customer does.
- A scheduled test submission. Monthly, from an address outside your own organization, through the live form. Put it in the maintenance checklist next to plugin updates.
- Provider-side data. If you send meaningful volume, Google Postmaster Tools V2’s Deliverability analysis panel sits under Compliance status and translates volume, spam-rate, engagement and authentication signals into a plain verdict—useful proof that authentication is table stakes, not the finish line.
What breaks it again
Form mail rarely degrades on its own. It breaks at specific moments, and every one of them is a reason to re-run the 15-minute protocol the same day:
- A domain transfer to a new registrar.
- A hosting move, or a DNS provider change—the zone gets recreated and one TXT record does not make the trip. Before you switch nameservers, confirm MX, SPF, every DKIM selector, DMARC and any verification TXT already exist at the new provider.
- Adding a system that sends as your domain: newsletter tool, store, CRM, booking, invoicing.
- A relaunch that migrates the site but not the mail configuration—new server, old plugin settings, no relay credentials.
- A CDN or proxy change. In Cloudflare, mail hostnames stay DNS-only and are never proxied.
Every DNS edit also has timing. Caches serve the old answer until the TTL expires, so a change is never instant and never risk-free—keep the previous value so you can put it back.
Questions I get asked every time
Does installing an SMTP plugin fix this on its own?
Sometimes, and only when the missing piece was the transport. An SMTP plugin pointed at an authenticated relay replaces mail() and gives you real errors and logs. It does not fix a From header carrying the visitor’s address, it does not publish your SPF or DKIM records, and it cannot open a port your host has closed.
Why does my form mail reach Gmail but not Outlook?
Receivers weight signals differently, so a marginal message passes one filter and fails another. Read the headers of the copy that did arrive: if SPF, DKIM and DMARC all pass and align, look at the recipient’s tenant rules, quarantine and junk folder. If they do not, you were borderline everywhere and one provider simply enforced first.
Is it safe to put the visitor’s address in the From field?
It is not safe in the practical sense: you are sending mail that claims a domain you are not authorized to send from, and modern receivers refuse or junk exactly that. Use your own domain in From and the visitor’s address in Reply-To. Replies still reach them.
Can my own DMARC policy block my own contact form?
Yes. If your domain publishes p=quarantine or p=reject and your website’s mail has neither aligned SPF nor aligned DKIM, receivers do what you asked and junk or refuse it. This is one of the most common causes of a form that “suddenly stopped working” months after a DMARC rollout that never inventoried the website as a sender.
How do I prove an inquiry was never delivered?
With logs, which is why you enable them before you need them. A relay log with a message ID and a rejection reason is evidence. A form plugin log that only says “sent” is not, because it is recording the local handoff described at the top of this article, not delivery.
What to do this week
Submit your own form to three different providers and read one header block. That is 15 minutes and it either ends the worry or gives you the exact line to fix. If it turns up misalignment and you would rather not spend a weekend on DNS, we run this check as part of Website Maintenance—or just send us the header and we will tell you which of the five causes you are looking at.