One hyphen broke my contact form

This site has a contact page. It has a form with a name field, an email field, a message box, and a Send button. It looks finished. Every time I opened it, it rendered perfectly.

It probably never sent a single message.

What I did

I didn't go looking for that bug specifically. I sat down to do something I had never done for this site: read every page as if someone else had written it, and check it against something other than my eyes. I went file by file, and I ran a small script that walked each HTML file and reported three things: tags that were opened but never closed, duplicate ids, and internal links that pointed at files that don't exist.

Here's what came back:

None of these produced an error. Not one. That is the whole story, really, but it's worth unpacking why.

Why none of it looked broken

Browsers are built to be forgiving. If an HTML file ends without closing <body> and <html>, the parser closes them for you. You don't get a warning in the page and you don't get one in the console. The page renders, minus a footer you might not notice is missing.

A form is forgiving in a different way: it doesn't know what the server wants. To the browser, access-key and access_key are equally valid names. It sends whatever you give it. The only part of the system that knows the name is wrong is the server on the other end, and the only time it can tell you is after someone presses Send. I had never pressed Send.

And a <title> lives in the browser tab, the one part of a page I never look at while I'm building it.

So I had three different kinds of failure, and they shared one property: the page looked right. My entire testing process was "open it and look at it." That process is blind to exactly these bugs.

Where they came from

Every one of them is a copy-paste fossil. When I need a new page, I duplicate an existing one and change the parts I'm thinking about. On the contact page, I was thinking about the form. I wasn't thinking about the <head>, so it kept the title of whatever post I copied it from. When I was cutting out the article body to make room for the form, I cut a little too far and took the footer and closing tags with it.

The access-key hyphen is a guess, but a reasonable one: HTML attributes are full of hyphens (data-page-id, aria-label, http-equiv), and my fingers have learned that pattern. Web3Forms uses an underscore. Nothing stopped my habit from winning.

The fix

The form fix was one character:

<!-- before -->
<input type="hidden" name="access-key" value="...">

<!-- after -->
<input type="hidden" name="access_key" value="...">

The contact page got a real title and description, and its footer and closing tags back. The rest were quick once I could see them: the Writeup link now points at a real post, the homepage lists posts newest first, and every link ends in a slash.

But the one-character fix isn't the interesting part. The bug was cheap to fix and expensive to have, because for as long as it was live, anyone who used that form would have filled it out, pressed Send, and been told it failed, or worse, assumed it worked and waited for a reply that was never coming. A broken contact form doesn't just fail; it fails at the exact moment someone was reaching out.

What I learned

"It renders" is not a test. In my bank API project I learned that a green test suite can sit on top of a crash. This is the front-end version of the same lesson: a page that looks finished can still be broken in every way that matters. Looking at a page checks the layout. It doesn't check the parts you can't see, and it can't check anything that happens after a click.

Forgiving systems hide bugs instead of preventing them. I used to think of HTML's leniency as friendly. It is, for getting started. But it means mistakes don't announce themselves; they accumulate quietly. When the tool won't complain, you need something that will. For me now, that's a small checker script and the W3C validator.

Test the whole path, including the part you don't own. My form's real job isn't to display; it's to deliver a message to my inbox. The only test that proves that is sending one. Every form I add from now on gets one real submission before I call it done.

When you copy a page, the parts you aren't thinking about come along too. The <head> is the easiest place for stale content to hide, because it never shows up on the screen. When I duplicate a page now, I edit the <title> and description first, before I touch anything else.

The audit took less than an hour. I'd been running the site for weeks without one. I'm adding it to the routine: every few posts, read the site like a stranger, and run the checks that don't depend on my eyes.

And if you tried to contact me before today and never heard back: it wasn't you. Please try again.

← All posts

Comments