# Your Form Is Silently Failing and You Don't Know

> Plaintext version for LLMs. Canonical HTML: https://askbowtie.com/learn/fixes/form-failures/

## Bottom line

A broken form almost never looks broken. Visitors click submit, see a spinner or even a "thank you", and the data never arrives. You find out weeks later, when someone mentions they emailed you twice.

askbowtie does not fix your form. It detects the failure the moment it starts, by joining the signals around the form: submit clicks that keep firing while completed conversions stop, JavaScript errors on the form page, rage clicks on the submit button, exit rate spiking on a page that used to convert. When the clicks continue but the conversions vanish, the form is failing, whatever the success message says. This page shows you how to catch it, then how to trace and fix it.

## The problem

Nobody reports a silently broken form. The visitor assumes you got their message. You assume leads are just slow this week. Both of you are wrong, and the form page itself looks perfectly normal.

The four ways forms fail, and why each one hides:

| Symptom | What the visitor sees | What's actually wrong |
| --- | --- | --- |
| Nothing happens | Click submit, no response | JavaScript error, disabled button |
| Infinite loading | Spinner that never stops | Request timeout, server error |
| Success but no data | "Thank you" message | Email not sent, data not saved |
| Validation error stuck | "Fix errors" but no errors shown | Client/server validation mismatch |

The third row is the killer: a hardcoded success message that appears whether or not the server ever confirmed receipt. The submit fires, the UI celebrates, and there was no server round-trip at all. Every submission is lost, and there is zero visible evidence.

## Detection: the signals a broken form emits

You cannot see a silent failure by looking at the form. You can see it in the data, because a breaking form changes several signals at once:

| Signal | Healthy form | Silently failing form |
| --- | --- | --- |
| Submit clicks on the form | Steady | Steady (visitors still try) |
| Completed conversions | Steady | Drops toward zero |
| JS errors on the form page | Rare | New error type, often after a deploy |
| Rage clicks on the submit button | Rare | Spikes (button appears dead) |
| Exit rate on the form page | Normal | Climbs (visitors give up) |

The tell is the divergence. Clicks without conversions means people are trying and failing. That gap is invisible in a pageviews chart and obvious the moment you compare the two signals side by side.

Bowtie watches all of these per page. Ask "did anything break on my contact page this week" and the answer joins errors, clicks, and conversions for that URL, with the numbers cited.

## A real read (demo data)

Here is what a silent failure looks like in the data. All figures below are demo data.

A site's `/contact` page normally does 40 to 50 form submissions a week. After a Tuesday deploy:

- Submit-button clicks: 44 last week, 41 this week. Visitors behave identically.
- Completed form conversions: 43 last week, 2 this week.
- New error incident: `TypeError: Cannot read properties of undefined (reading 'validate')` on `/contact`, first seen Tuesday 14:20, affecting 96% of sessions on that page.
- Rage clicks on `button.submit-btn`: up from 1 to 19.

Read together, the story is unambiguous: the deploy shipped a JavaScript error that breaks the submit handler, visitors click a dead button, some click it five times, all of them leave. Roughly 39 lost leads in one week. The success message still worked, so nobody reported it.

Without the join you have four dashboards that each look "a bit off". With it you have one answer: the form broke Tuesday at 14:20, here is the error, here is the button, here is what it cost.

## Diagnosis: find where the submission dies

Once detection tells you the form is failing, trace the submission from browser to inbox. Open DevTools (F12), go to the Network tab, and submit the form yourself.

| What you see in the Network tab | Problem location | Next step |
| --- | --- | --- |
| No request sent | Frontend JavaScript | Check console for errors |
| Request pending forever | Server not responding | Check server logs |
| Request failed (red) | Network or server | Check the status code |
| Request succeeded, UI stuck | Frontend response handler | Check response format + JS errors |

If the request goes out but fails, the status code narrows it down:

| Failure | Cause | Fix |
| --- | --- | --- |
| CORS error | Submitting to a different domain | Configure CORS headers or use same-origin |
| 401/403 | Authentication required | Include credentials, check the CSRF token |
| 500 | Server-side crash | Read the server error log for the exception |
| Network error | Connectivity issue | Add retry logic and offline handling |
| Timeout | Server too slow | Optimize the backend, or process async |

One trap: a 200 response with an error in the body is still a failure. Read the actual response content, not just the status code.

Then follow the data the rest of the way:

1. **Reproduce it.** Can you make it fail consistently?
2. **Console.** JavaScript errors before or during submission?
3. **Network.** Does the request go out? What comes back?
4. **Server logs.** Did the request arrive? Did processing throw?
5. **Storage.** Is the submission saved even if the email failed?
6. **Isolation.** Does it work in incognito, or a different browser?

Quick test: submit with minimal data, just required fields and simple values. If that works but real submissions fail, the problem is validation or data handling, not the form itself.

## The email leg: sent but never arrives

The most common "missing submission" is not a form bug at all. The submission was received and stored; the notification email died in transit. This is endemic on WordPress, where forms hand email to PHP's `mail()` function by default, and PHP mail has been unreliable for a decade.

| Failure point | What happens |
| --- | --- |
| Host disabled PHP mail | Email never sends |
| No SPF/DKIM records | Recipient rejects it as spam |
| Shared IP blacklisted | Email blocked before delivery |
| Wrong "from" address | Fails SPF alignment |

**Check first:** does your form plugin store submissions? Most do. If entries appear in the plugin but emails don't arrive, the form works and email delivery is the problem. Also search every mail folder for "wordpress@": many hosts use it as the default sender and it gets filtered constantly.

**The fix:** stop relying on PHP mail. Route email through SMTP and a transactional service (Resend, Brevo, SendGrid all have free tiers), add the SPF and DKIM records the service gives you, and send a test. This resolves the large majority of form email problems in under an hour. Full WordPress walkthrough: [WordPress fixes](/learn/fixes/wordpress-fixes/).

And treat email as a notification, never the system of record. Store submissions server-side so an email failure loses nothing.

For the real-world playbook on spotting and fixing silently broken forms in production, see [how I find a silently broken form](/guides/find-broken-forms/).

## Prevention that actually holds

**Tie success to the server.** Show "thank you" only after the server confirms the write. A hardcoded success message is a data-loss machine.

**Store first, email second.** Database is the record; email is the alert.

**Watch the divergence, not the form.** Manual test submissions catch the failure you thought to test for. Monitoring submit clicks against completed conversions catches the ones you didn't. A form that breaks on Tuesday should be a question you can answer on Tuesday.

**Handle every response type.** Success, validation error, server error, timeout, network failure: each needs distinct user feedback, and the button should disable after the first click.

## Questions you can now answer

- Did my contact form break this week? -> compare submit clicks to completed conversions for the form page; divergence is the answer.
- When exactly did it break? -> the first-seen timestamp on the new error incident for that page.
- Which element is broken? -> the incident detail: failing URL, error message, and the CSS selector visitors are rage-clicking.
- How many leads did it cost? -> conversions per week before the break, minus after.
- Are visitors giving up on the page? -> the form page's exit rate against its baseline.
- Is the form fine but the email dying? -> submissions stored but notifications missing points at the email leg, not the form.

## MCP reference (for agents)

Detection signals for a silently failing form, and the tool that carries each:

Tools:

- `get_conversions { domain, period? }` -> conversion counts, rates, and top converting pages. A drop on the form's conversion while traffic holds is the primary alarm.
- `get_incidents { domain, period? }` -> active issues ranked by business impact. A new error type scoped to the form page is the usual cause.
- `get_incident_detail` -> drill into one incident: failing URLs, error messages, CSS selectors. This names the broken element.
- `get_engagement { domain, page, period? }` -> per-section clicks and downstream conversion for the form page. Submit-section clicks holding steady while its conversion_rate collapses is the click-without-completion signature.
- `get_top_pages { domain }` sorted by exit_rate -> a form page whose exit rate climbs above baseline is bleeding visitors mid-task.
- `get_traffic { domain, period? }` -> rules out the boring explanation (traffic to the form page simply fell).

Question to tool:

- "did the form break" -> get_conversions (form page conversion trend) + get_engagement (clicks vs conversion on the submit section).
- "when and why" -> get_incidents scoped to the form page, then get_incident_detail for the error message and selector.
- "what did it cost" -> get_conversions, before-window vs after-window.
- "are people giving up" -> get_top_pages by exit_rate, plus rage-click incidents on the submit selector.

Recommended agent loop: get_conversions for the form page over the last 14 days; if conversions diverge from traffic, get_incidents for the same window; get_incident_detail on anything first-seen near the drop; confirm with get_engagement (submit clicks steady, conversion collapsed); report the break time, the failing element, and the lost-lead count, every number cited.

Gotcha: a conversion drop with a matching traffic drop is a traffic problem, not a form problem. Always read get_traffic before declaring the form broken.

## Related pages

**Pillar:** [Fixes & Troubleshooting](/learn/fixes/)

**Related:**

- [WordPress Fixes](/learn/fixes/wordpress-fixes/) -> the SMTP setup walkthrough and plugin-conflict diagnosis
- [Error Monitoring](/learn/error-monitoring/) -> how JS errors and broken elements are caught in the first place
- [Rage Clicks](/learn/error-monitoring/rage-clicks/) -> the dead-button signal in depth
- [Conversion Tracking](/learn/conversions/) -> how completed submissions are counted

Docs: [https://askbowtie.com/docs/](https://askbowtie.com/docs/) · Connect a site: [https://askbowtie.com/](https://askbowtie.com/)
