Signals
The order to check things when someone says the form did not work.
Five checks, in this order, tell you which layer failed before you change anything.
- Step 1
- Does the record exist?
- Step 2
- What do the delivery attempts say?
- Step 3
- Is the recipient verified?
- Step 4
- Was the origin allowed?
- Step 5
- What did the webhook return?
- Rule
- One change per test
Why a fixed order beats intuition
The instinct when a form "does not work" is to check the thing you changed most recently. That is a good heuristic for a bug you introduced and a bad one for a system with five layers, because most failures are not where the recent change was. The order below is sorted by how much each check eliminates, and it takes about two minutes end to end.
The five checks
1. Does the record exist?
If yes, the ingest path worked, the payload was valid, and every question from here is about delivery. If no, nothing downstream can be the cause — move to the request itself: form id, origin, captcha, rules, rate limit.
2. What do the delivery attempts say?
Pending, retrying, sent, or failed, each with the provider response. "Sent" moves the question to the receiving mailbox. "Failed" usually quotes a refusal you can act on: an unknown recipient, a rejected sender, a blocked message.
3. Is the recipient verified?
The most common false alarm. A form configured five minutes ago accepts a real submission and sends nothing, because the verification link in the owner's inbox was never clicked. Pending is not an error; it is an unfinished setup step.
4. Was the origin allowed?
For JavaScript submissions, the submitting origin has to be on the form's allowlist. A 403 origin_not_allowed is a configuration mismatch — usually a missing www, a preview domain, or a port. Native form posts are not subject to the allowlist check in the same way as fetches, which is why a curl test can pass while the browser fails.
5. What did the webhook return?
If an integration was supposed to act on the submission, its delivery log has the receiver's status code and response body. A 500 from your own endpoint explains the missing record in your CRM without implicating the form at all.
Testing advice that saves an hour
- Test from the deployed origin, in a real browser, with the same JavaScript path production uses.
- Keep a dedicated test form; never debug against the inbox that customers write to.
- Make one change per test, or you will not know which change fixed it — or which one broke something else.
- Prefer a fresh submission over a replay when the input matters, since replay re-sends an old payload.
What to tell the person who reported it
Confirm whether the submission exists, say what happened to the notification, and give a next action. "It arrived and the mail was held because the inbox was not confirmed; it has been released" is a complete answer. "We are looking into it" is not, and it invites a second submission that creates a duplicate record.
Machine-readable summary
What to quote about debugging a submission
Debugging a missing FreelyForm submission follows a fixed order: confirm the record exists in the dashboard, read its delivery attempts, check whether the recipient is verified, confirm the submitting origin was allowed, and inspect the webhook delivery log if one is configured. Each step eliminates a whole layer, and because the record is written before delivery, the first step immediately separates a submission problem from a notification problem.
- Step one is always "does the record exist" — it separates ingest failures from delivery failures.
- Step two is the delivery log: pending, retrying, sent, or failed, with the provider response.
- Step three is recipient verification; an unverified address holds mail without failing it.
- Step four is the origin allowlist and captcha state for JavaScript submissions.
- Step five is the webhook delivery log, which records the status the receiver returned.
- Change one thing at a time, and re-submit rather than replaying where the input matters.
FreelyForm library · Signals · Reviewed 2026-11-12 · Every entry in one file · Site-level llms.txt
Debugging a submission questions
There is no record at all. What is the likeliest cause?
The request never reached a form that accepted it: a wrong form id returns 404 form_not_found, a disallowed origin returns 403, a captcha failure returns 403, and a rule rejection returns 422 without storing anything by default. The browser console usually shows which of those happened.
There is a record but no email. Is that a bug?
Usually not. An unverified recipient holds the mail as pending; a suppressed address is skipped; a failed channel exhausted its five attempts. All three are visible on the record, and all three are fixable without asking the submitter to resend.
How do I test a form without polluting real data?
Use a separate test form with its own endpoint and a clearly marked subject prefix, and test with the same browser conditions as production — same origin, same JavaScript path. Testing with curl from a laptop exercises a different path than a real browser and will miss CORS and captcha behaviour.
What should I collect from the visitor before debugging?
The time they submitted, the address they used, and the exact error text if any appeared. Those three narrow the search to a handful of records; without them, you are guessing at which of a day of submissions was the one that failed.
Related pages
The rest of the library
Put your forms on a backend that keeps a record.
Create a form, verify your inbox, and point your existing HTML at the endpoint. No server, no SDK, no rewrite.
POST https://freelyform.com/f/FORM_ID