Skip to content
  • FG-74
  • 10 min read
  • Last checked November 7, 2026

WordPress Form Submits but No Entry or Email Arrives: How to Find Where It Breaks

The form says “sent” or spins forever, but no entry or email ever shows up. Find out whether the submission or the notification failed, then fix that half.

Run the health check

A visitor fills in your form and clicks Send. Sometimes they see “Thank you for your message. It has been sent.” Sometimes the button spins forever, or they get “There was an error trying to send your message. Please try again later.” Either way, nothing reaches your inbox, and you only find out when a customer asks why you never replied.

A form submission is a chain: the browser sends the data, something may filter it on the way, the form plugin checks and saves it, and then WordPress hands an email to a mail server. A break anywhere looks the same from the outside. This guide shows how to find the broken link in a few minutes, so you fix the right thing. Mail that leaves WordPress and then disappears is covered in depth in the guide on WordPress not sending email; this guide gets you to that point and covers everything before it.

What is happening

Most form plugins submit with JavaScript in the background (AJAX) to /wp-admin/admin-ajax.php or a REST route under /wp-json/. The plugin then runs validation and anti-spam checks, saves an entry if it stores entries, and calls wp_mail() for each notification. The message you see on screen is chosen by the plugin based on the result.

Stage What breaks there Where you see it
Browser JavaScript error, script delayed or combined by a speed plugin Console tab; no request appears in the Network tab
Network edge Cloudflare or server firewall blocks the POST Network tab shows 403 with an HTML block page
Security check Expired nonce or token from a cached page, failed CAPTCHA Response reports spam, invalid nonce, -1 or 403
Plugin logic Validation, spam filtering, PHP error Response JSON; PHP error log
Notification Wrong recipient, conditional logic, bad From address Entry exists but no email; mail log
Delivery Mail server rejects or spam-folders the message Mail log, bounce, spam folder

One trap deserves a mention up front: Contact Form 7 shows the same default text, “There was an error trying to send your message”, when mail fails and when the submission is judged to be spam. The screen cannot tell you which happened. The response in the Network tab can.

Before you start

You need an administrator login, a desktop browser with developer tools (Chrome, Edge or Firefox), and ideally SSH or your hosting panel log viewer.

No backup is needed for the diagnostic steps. Back up before editing server configuration or adding code.

How to run commands: SSH into the server and change into the site folder, the one containing wp-config.php. WP-CLI may be called wp or wp-cli on your host. curl runs on your own computer; macOS and Linux include it, and Windows 10 and later include curl.exe, or use WSL. The diagnostic tools can check headers from a browser.

Step 1: Decide which half failed

Submit a test with a unique word in the message, such as TESTQUOKKA, and note the time. Then look for that entry inside WordPress:

  • Gravity Forms, Fluent Forms, Formidable, Ninja Forms and WPForms (paid tiers) have an Entries screen under the plugin menu.
  • Contact Form 7 does not store submissions on its own. Install Flamingo from the same author and it saves every message under Flamingo, Inbound Messages.
  • WPForms Lite does not show full entries in the dashboard, so rely on Step 2 and the email log instead.
  • Page builder forms such as Elementor Pro store submissions under their own Submissions screen if that option is on.

If the entry is there, the submission worked and the notification failed: skip to Step 5. If it is missing, or the plugin cannot store entries, carry on with Step 2.

Step 2: Read the response in the Network tab

Open the form page, press F12, select the Network tab and check Preserve log. Submit the form. Click the request that appears (usually admin-ajax.php or feedback) and read the Status column, then the Response panel.

What you see What it means Go to
No request appears at all JavaScript never sent it Step 3
200, JSON with "status":"mail_sent" (Contact Form 7) or a success flag Plugin accepted it and wp_mail() returned true Step 5, then delivery
200, "status":"mail_failed" wp_mail() returned false Step 6
200, "status":"spam" Anti-spam check rejected it: CAPTCHA, Akismet, honeypot or nonce Step 4
200, "status":"validation_failed" A field failed server-side rules, often a hidden or conditional field Check the invalid_fields list in the JSON
400 with body 0 from admin-ajax.php No code handled that action: the plugin is inactive, or a filter unhooked it Plugin and PHP error log
403 with body -1 WordPress nonce check failed Step 4
403 with an HTML page A firewall blocked the POST Step 4, firewall section
500 PHP fatal error during processing PHP error log at that time

Copy the response text. It is the single most useful thing you can send a developer or your host.

Step 3: JavaScript errors and speed plugins

If no request appears, open the Console tab and reload. A red error before you click Send, such as jQuery is not defined or wpcf7 is not defined, means the form script did not load or loaded in the wrong order.

The usual cause is a speed plugin that delays, defers or combines JavaScript. Test by loading the page with the plugin bypass parameter or with optimization switched off for that page. The guide on menus, sliders and forms broken by speed plugins covers the bypass parameters, exclusions and how to find the script that needs excluding. Theme or plugin conflicts are the other cause: disable plugins one at a time on a staging copy until the error stops.

Step 4: Expired tokens on cached pages, and firewalls

Why caching breaks forms after a few hours

Many forms include a WordPress nonce, a short-lived security token. WordPress nonces are valid for 12 to 24 hours by default. For logged-out visitors every person gets the same nonce during that window, so a cached page works for a while. If the page cache keeps the page longer than the nonce lives, every visitor gets an expired token and every submission fails, often with a 403 and -1, or a “spam” result.

The pattern is distinctive: the form works right after you clear the cache, then breaks the next day. Some plugins use their own time-based anti-spam token, which can fail the same way.

Contact Form 7 only checks its nonce for logged-in users. That is why an editor who is logged in can see failures that anonymous visitors never see, if a cache serves them a stale copy.

Confirm the page is cached

Run this on your own computer:

curl -sI https://example.com/CONTACT_PAGE/ | grep -iE "age|cache|expires"

example.com/CONTACT_PAGE/ is the full URL of your form page. -s hides progress and -I fetches only headers. grep -iE filters for caching headers without caring about case. Headers such as cf-cache-status: HIT, x-cache: HIT, x-litespeed-cache: hit or a large age value (seconds since the copy was made) mean the page comes from a cache. An age above 43200, which is 12 hours, is in the danger zone for nonces.

Fix it

  • Exclude the form page URLs from your page cache plugin and from any CDN cache rule. This is the most reliable fix.
  • Or keep the cache lifespan under 12 hours so pages are rebuilt before their nonces expire.
  • If the form appears on every page (footer or popup), use the form plugin’s option to load or refresh the token with AJAX, if it has one.
  • Clear every cache layer after the change, then test in a private window.

Expired nonces inside wp-admin, with “The link you followed has expired”, are a different scenario covered in their own guide.

Firewall blocks

A 403 response with an HTML page rather than JSON means something in front of WordPress refused the POST. Cloudflare block pages include a Ray ID; ModSecurity pages often say “Not Acceptable” or “Forbidden” with no WordPress styling. Words in the message such as SQL keywords, script tags or long URLs can trigger a rule. The 403 Forbidden guide shows how to find the rule ID and ask for a scoped exclusion rather than turning the firewall off.

Step 5: Check notification routing

If the entry exists but no email arrived, open the form notification (or Mail) settings and check each field. Most “lost” form emails are routing mistakes, not delivery failures.

Send To

Look for typos, an old staff address, or a merge tag such as {admin_email} that resolves to the site admin email in Settings, General, which may be a developer or an old address. Put a real address in directly while testing.

From versus Reply-To

Header Should be Why
From An address on your own domain, such as website@example.com Receiving servers check SPF, DKIM and DMARC against the From domain. A visitor address in From fails those checks and is often dropped silently.
Reply-To The visitor email field, using the form merge tag Clicking Reply in your mail app goes to the visitor, without pretending the message came from them.

If your From field currently uses the visitor email merge tag, that alone can explain mail that works for some senders and vanishes for others, typically anyone with a Gmail, Yahoo or Outlook address.

Conditional logic and disabled notifications

Gravity Forms, WPForms, Fluent Forms and Formidable can send a notification only when a field matches a value, for example only when “Department” equals “Sales”. Check that each notification is active and that its conditions match your test. A renamed dropdown option can quietly break a condition that worked for years.

Step 6: Prove whether WordPress handed the email over

Install an email logging feature (many SMTP plugins include one, or a dedicated mail logging plugin). Submit a test, then check the log. If the email appears with a success status, WordPress did its part and the problem is delivery. If it shows an error, or nothing at all appears, the problem is inside WordPress.

You can also test wp_mail() directly on the server:

wp eval 'var_dump( wp_mail( "YOU@example.com", "wp_mail test", "Sent from WP-CLI" ) );'

YOU@example.com is an inbox you can check. wp eval runs the PHP inside WordPress, so mail plugins and SMTP settings apply. Good output is bool(true) and the email arriving. bool(false) means WordPress could not hand the message to the mail system. bool(true) with no email means the message left WordPress and was lost later. Both of those cases continue in the guide on WordPress not sending email, which covers SMTP setup, PHP mail and authentication records.

Advanced: trace one submission end to end

When the simple checks disagree, follow a single submission through every log using its UTC timestamp.

Log the reason wp_mail fails

WordPress fires the wp_mail_failed action with an error object whenever sending fails. Create a file in wp-content/mu-plugins/, for example wp-content/mu-plugins/log-mail-failures.php, with this content:

<?php
add_action( 'wp_mail_failed', function ( $error ) {
    error_log( 'wp_mail_failed: ' . $error->get_error_message() );
} );

There are no placeholders. Files in mu-plugins load automatically. After the next failed submission, the PHP error log will contain a line such as wp_mail_failed: Could not instantiate mail function. or an SMTP error like SMTP Error: Could not authenticate. The first points to PHP mail being unavailable on the server; the second to wrong SMTP credentials. Delete the file when you are done.

Match the request in the access log

grep "POST ENDPOINT_PATH" ACCESS_LOG | grep "DD/Mon/YYYY:HH:MM"

ENDPOINT_PATH is the URL path from the Network tab, such as /wp-admin/admin-ajax.php. ACCESS_LOG is your access log path from the hosting panel, or /var/log/nginx/access.log or /var/log/apache2/access.log on your own server. DD/Mon/YYYY:HH:MM is the minute of your test in the log format, such as 07/Nov/2026:14:32; note that many servers log in UTC. Good output is one line with status 200. No line means the request never reached the server (a CDN blocked it or JavaScript failed). A 403 or 500 here matches the firewall and PHP error cases above.

Check the mail server log

If you manage the server, search the mail log for your test recipient. On Exim systems it is often /var/log/exim_mainlog; on Postfix, /var/log/mail.log or /var/log/maillog. A delivery line shows the message was accepted by the next server; a rejection line includes an SMTP code that the bounce codes guide explains. On shared hosting, ask your host to search it for you.

When to contact your host

Open a ticket when the request is blocked before WordPress, when wp_mail() returns false, or when the mail log shows rejections. Send:

  • The form page URL and the endpoint path.
  • The UTC time of a test submission and the test recipient address.
  • The HTTP status and response body from the Network tab.
  • Any Cloudflare Ray ID or firewall rule ID.
  • Matching lines from the PHP error log and the wp_mail_failed message.
  • Your From, Reply-To and Send To settings, and what changed recently.

If the whole site is failing, not just the form, start at the Emergency Room.

Still stuck?

Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.

Get the free checklists

Keep reading

All field guides

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close