The worst bug I have shipped was a contact form that worked perfectly except for the part where it sent the message.
It validated. It showed the success state. The little tick appeared and the fields cleared and everything about the experience said received. What arrived at the other end was an email with an empty name field, and it had been doing that for a while before I noticed, because nothing about a form like that looks broken. Nobody complains about a form that told them it worked.
This is the failure mode that makes contact forms worth a whole article. A form that throws an error gets reported within a day. A form that lies gets reported never.
The bug, because it is a good one
The field was named name. The code read it as form.name.
That looks correct and is not, and it is not a typo — it is the DOM behaving exactly as specified. An HTML form element has its own name property, part of the interface every form has, which returns the form's name attribute. When you also have an input called name inside it, the built-in property wins. So form.name quietly returned the form's own name — usually an empty string — instead of what the visitor typed.
Every other field worked. form.email, form.message, form.subject — none of those collide with anything, so all of them behaved as expected. Only name was shadowed, which is why the bug looked so specific and so baffling.
The fix is one line:
const name = form.elements.namedItem('name').value;
form.elements is the collection of actual fields. It contains only what is in the form, so nothing can shadow anything. If you take one practical thing from this article, take that: read fields through form.elements, not off the form object. The same collision waits for a field named action, method, target, length, submit or reset, and submit is the one that will really ruin an afternoon, because naming a button submit overwrites the function you call to submit the form.
I did not find this by reading the code. I found it because I had written a test that filled in the form and asserted on what the send function received, and the name arrived empty. It is the single clearest argument I have for testing the boring parts.
The other ways forms lose messages
Once I started looking properly, the field-shadowing bug turned out to be the least common problem. These are the ones I keep meeting.
The success message fires regardless
Somebody writes the happy path first, wires up the success state, and never comes back to handle the rejection. So the "thanks" appears when the request is sent, not when it succeeds. Every network failure, every rate limit, every expired key becomes an invisible loss. If your success handler is not attached to a resolved response, you do not have a success handler, you have a timer.
The third-party service is a single point of failure
Most small sites send mail through a service rather than a server, which is sensible — deliverability is somebody else's problem. But it means your enquiries now depend on a script loading from another domain. If that request is blocked by an ad blocker, a corporate proxy, or a bad minute on their end, your form has no way to send anything.
This one has a good answer. Keep a fallback: if the library did not load or the send failed, do not show an error and stop — build a mailto: link with the message already in it and put it in front of the visitor. They typed all that. Do not make them type it again, and do not make the outcome of their effort depend on whether a CDN was having a good day.
Spam protection eats real enquiries
Honeypot fields are the polite way to catch bots: an input hidden from humans that only a script would fill in. They work, and they also break when a password manager helpfully fills the hidden field for a real person. Aggressive keyword filters do something similar — I have seen a filter that rejected any message containing a URL, on a form used by people who wanted to send their website address.
Nothing is stored anywhere
If the only copy of an enquiry is an email, then a delivery failure is a permanent loss and you will never know it happened. Even a plain log file that records the time, the address and the message body means a bad week is recoverable. This costs almost nothing and is the difference between "we had an outage" and "we lost a month of leads".
How to check yours in five minutes
Do this on your own site now rather than believing the form works because it worked when it was built.
- Send a real test with a distinctive subject, from a device that is not yours, and confirm it arrives with every field populated. Empty fields are the tell.
- Open the network tab, block the mail provider's domain, and submit. What does the visitor see? An error is acceptable. A success message is a bug.
- Submit with JavaScript disabled. Does the form fall back to something, or is it inert?
- Check the spam folder of the receiving address, then check whether that address still exists. Forms outlive the inboxes they were pointed at.
- Confirm the recipient address is right. This sounds too obvious to check. On this site I found two different contact addresses in use — one in the footer, another in the form configuration and the structured data — because the footer was written by hand and never updated. Half the paths to me pointed at an old inbox.
Why this is worth more than most SEO work
I spend most of my time getting people to a website. A broken form means the entire cost of that — the audit, the content, the rankings, the waiting — is spent delivering someone to a door that does not open.
The arithmetic is unkind. Recovering ten per cent more enquiries from traffic you already have is usually cheaper and faster than winning ten per cent more traffic, and unlike rankings it takes effect the moment you deploy. It is the same argument I made in why your website gets traffic but no customers, one step further down the funnel.
It is also the same class of failure as the one in my site returning 200 while serving nothing: a system reporting success while doing nothing. Those are the expensive bugs. Anything that fails loudly gets fixed. Build things that fail loudly.
If you want someone to look at yours, that is part of what I do — and you can test the theory by using the form on this site, which I have now checked rather more carefully than I once did.