Your form has a name field, an email field and a button that says send. It does not deliver anything. It does not fail, it shows no error, it does not break: the page blinks, the fields clear, and everything looks like it went fine. Which is why it can sit like that for months.
This is the normal state of a form on a static site, and it is not an oversight by whoever built it. The AI that wrote your site wrote the form correctly. What is missing is the half that is not HTML.
Why an HTML form sends nothing
A <form> knows how to do exactly one thing: package up what the visitor typed and deliver it to an address. That address is the action attribute. The browser always does its part; what decides everything is what sits on the other side.
And a static site is files — HTML, CSS, images, JavaScript — served as they are. On the other side nobody is listening: no program to receive that package, store it and tell you. That is why the form does not fail. It never got as far as trying.
It is the same border that separates a site from an app: publishing files is one thing, receiving something back is another. A form is the first thing that crosses it.
How to tell in thirty seconds whether yours is dead
No tools needed. Open your published site, hit Ctrl+U (Cmd+Option+U on Mac) to view the source, and find the <form> tag:
| What your code says | What happens when someone submits |
|---|---|
<form> with no action, or action="#" | The page reloads onto itself. The submission is gone, with no record that it existed. |
action="mailto:you@…" | It tries to open the visitor’s mail program. On a phone, usually nothing happens at all. |
an onsubmit that shows "Thanks!" | The thank-you message is real; the submission is not. The most deceptive of the three, because it confirms something that did not happen. |
action="https://…" | It does leave the browser. Now the question is where it lands and who tells you. |
And the test that settles it, which takes ten seconds: fill it in and send it from the published site, not from your machine. If nothing reaches you anywhere within two minutes, you have your answer.
What not to do
- Do not solve it with
mailto:. It depends on the visitor having a mail program set up, and on a phone almost nobody does. The ones who do watch an app they did not ask for open with a half-filled draft, and close it. It also leaves your address written in the HTML, which is exactly where spam harvesters look for it. - Do not put an email service’s API key in the JavaScript. It is what usually comes out of asking the AI to "make the form send an email", and that key ends up published to anyone who opens your site — which keys get published, and how to check.
- Do not stand up a backend for three fields. A server that exists only to receive a form is a piece you will maintain, update and pay for over years. The work does not end the day it works.
The options that actually work
All of them do the same thing: put somebody listening at that address. What separates them is how much they cost you to keep:
- A form service: you point the
actionat a URL of theirs, they store the submission and notify you. The least work, and one more account. - Your own serverless function: full control over what happens to each submission, in exchange for code to maintain and one more bill to watch.
- The platform your site already lives on, if it includes it. Same idea as the first, without the extra account or a second place to go looking for your leads.
How it works on FluentOps
Forms ships with the platform. You turn it on per project and it hands back an address of your own; there is no library to install and no JavaScript to write.
- On your project, turn on Forms. It gives you an endpoint with your token:
https://api.fluentops.org/forms/YOUR_TOKEN. - Put that address in your form’s
action, withmethod="POST". - Publish. Your field names stay whatever they already were — nothing to declare, nothing to configure.
<form action="https://api.fluentops.org/forms/YOUR_TOKEN" method="POST">
<input name="name" />
<input name="email" type="email" />
<textarea name="message"></textarea>
<!-- Bot trap: if it gets filled in, the submission is stored flagged and you are not emailed. -->
<input name="_gotcha" style="display:none" tabindex="-1" autocomplete="off" />
<!-- Where the visitor goes after submitting. -->
<input type="hidden" name="_redirect" value="https://yoursite.com/thanks.html" />
<button type="submit">Send</button>
</form>Every submission lands in your project’s inbox in the portal and emails you right away. The two hidden fields above are optional and worth having: _redirect sends the visitor to your own thank-you page (without it they go back to the page they came from), and _gotcha is a bot trap — a field a person never sees and a robot fills in. If it arrives with anything in it, the submission is stored flagged as spam and does not interrupt you.
If you have more than one form on the same site, add form-name with whatever name you like and the inbox keeps them apart. The limits, so you do not meet them on the day you hit them: 100 submissions a month on the free plan — across the whole account, not per project — and 1000 on Pro; 256 KB per submission, up to 100 fields, 8000 characters each.
And one decision worth knowing about: past the limit, the submission is still stored. You get told you went over, but nothing is dropped. A lost lead never comes back, and punishing the person who wrote to you over a ceiling of yours is the worst possible way to collect.
The step almost nobody takes
Wiring it up is not finishing it. Send yourself a test from the published site, from your phone, the way a visitor would — and check that it arrived in both places: the inbox and your email. It is the only moment you verify the whole chain, and it is exactly the step that was missing the first two times your form "worked".
Do it again every time you move the site to a new address or rebuild the contact page. A broken form looks identical to a working one.
And if your site is not online yet, here is how to publish it — you can wire the form up afterwards, on the same project.