Manual
Seven places a contact form can lose a lead before anyone notices
The form submits, a green message appears, and everyone moves on. That message is evidence about one thing only: what the page displayed. Here is the trace to run instead, and the file you keep when a client asks whether the enquiry ever arrived.

Thanks. Your enquiry has been sent.
That string lives in a template. A browser painted it because some code took its success branch, and it asserts something the page has no way to observe. The observation is real and the interpretation is narrow: the page displayed a confirmation. It says nothing about a database, a mailbox or a person.
Thanks. Your enquiry has been sent.EstablishesThe page rendered a confirmation string after the submit control was used.
Does not establishThat any system beyond the page received, stored or forwarded the submission.
Between that click and a lead somebody can answer sit seven separable stages. Each is owned by a different party, fails differently, and leaves a different artifact behind. A test that stops at the confirmation has inspected one of them, and not the one that loses enquiries.
| Stage | What you do | Evidence you keep | Failure you would see | Who owns the fix |
|---|---|---|---|---|
| Browser validation | Submit empty, then with awkward values | The value and the message shown | A legitimate value refused | Markup author |
| Request dispatch | Watch the network panel | Method, path, response status | Confirmation after a failed request | Front end |
| Request handling | Find it in the server log | The handler's own log line | A 200 with an error inside it | Endpoint owner |
| Storage or CRM entry | Search for a marker you planted | Record identifier and timestamp | No row, or a silent merge | Integration owner |
| Notification delivery | Read the delivery record, then the mailbox | Delivery status and message identifier | Accepted by a relay, never in a mailbox | Sending domain owner |
| Reply routing | Reply, and confirm arrival | The reply, received | Replies reaching nobody | Inbox owner |
| Audit record | Write the receipt now | The receipt itself | An argument with no file behind it | You |
No external scanner runs that trace for you, and ours does not try. AgencyProof does not submit forms: form submission is recorded as a check that was not performed, and checkout and payment flows are not attempted. AgencyProof inspects a bounded sample of reachable public pages. The report records the pages and methods used, plus important checks that were not performed.
Watch the browser decide the form is valid
Leave a required field empty and submit. The browser refuses and shows its own
message. That refusal is constraint validation, and the HTML Standard is exact
about what it reports. On checkValidity(): "Returns true if the element's
value has no validity problems; false otherwise. Fires an invalid event at
the element in the latter case."
It is a statement about values sitting in controls, evaluated in the page, before anything is dispatched anywhere. A form that passes has established that its own fields satisfy their own constraints, and nothing else has happened yet.
Two failures live here and they point in opposite directions. The loud one is a
field that should be required and is not, so an enquiry arrives with no way to
answer it. The quiet one is worse: a pattern or a type that refuses a
legitimate value. An email rule that rejects a plus sign, a phone rule that
rejects a country code. That visitor is stopped here, produces no request,
appears in no log, and is invisible to every later stage. You find it only by
typing awkward values on purpose.
Watch the request leave the browser
Network panel open, submit, and look for the request. This is where the two kinds of form part company. A form the browser submits natively performs a navigation: the request goes out, and if the handler answers with a redirect the browser follows it to a page the server chose.
POST /enquiry HTTP/1.1
host: fixture.localhost:8787
content-type: application/x-www-form-urlencoded
HTTP/1.1 303 See Other
location: /thanks
That thank you page is better evidence than it looks: it exists because the handler ran and issued the redirect. A confirmation rendered in place by client script is a weaker claim, because it says only that the client took its success branch, and success branches are sometimes written to run whether or not the request succeeded.
So for a scripted form the question is not whether the message appeared. It is what status came back, and what the client does when one does not. Disconnect the network, submit, and watch. If the confirmation still appears, you found the defect before your client did.
Read the handler's own record
Get to the server's own log and find your submission in it. This is the first artifact in the trace that a browser cannot produce.
AgencyProof's contact form writes the whole lead as one structured line before it attempts either storage or email, and the comment above it says why: the lead stays recoverable from server logs even if both of those fail. That line is the handler's testimony about what it received.
The same file carries a warning for anyone who tests forms with an automated filler. A hidden honeypot field is checked before anything else, and when it is filled the action returns success and stores nothing. A bot gets a quiet confirmation. So does a tool that fills every input on the page, including the one no human can see. The confirmation is correct, the absent record is correct, and with a browser as your only instrument the form looks fine. A design choice, not a defect, and the sharpest argument here for reading the log instead of the screen.
Find the row, or find nothing
Search the store for the submission you just made, using a marker you planted rather than your own name. A build reference in the message body works.
Two failures hide here. The first is a write that failed and told nobody.
AgencyProof's own action wraps its intake call in a try and catch, records
the error, and carries on, because an internal storage failure should not tell
a visitor their enquiry did not go through when the notification path may still
have carried it. It also means the visitor's screen cannot be evidence of the
write, which is the point.
The second is deduplication. Submit twice from the same address and a CRM may merge the second into the first, leaving no new record and no error. Testing repeatedly with your own address is how most people never see this. Vary the address.
Follow the notification past the send call
A send call that returns without error means the sending library got an answer it liked. Here is that answer, from the fixture's mail catcher:
2026-07-14T09:12:44Z fixture-mta out: to=<studio@fixture.test>
relay=mx1.fixture.test[203.0.113.10]:25 dsn=2.0.0
stat=Sent (250 2.0.0 Ok: queued as 4Wm1kP3xyz)
RFC 5321 is explicit about what that acceptance transfers: "once the server has issued a success response at the end of the mail data, a formal handoff of responsibility for the message occurs: the protocol requires that a server MUST accept responsibility for either delivering the message or properly reporting the failure to do so."
Responsibility, not arrival. The same specification separates a delivery system,
which passes mail to a user agent or a message store, from a relay system,
which transmits it onward to another server. The host that said 250 may have
been relaying, and the word queued in that log line says as much.
Authentication policy is the next place a message goes quiet. Under DMARC, a
domain publishing p=quarantine asks receivers to treat failing mail as
suspicious, which the specification says "can mean 'place into spam folder',
'scrutinize with additional intensity', and/or 'flag as suspicious'". And the
receiving side keeps its discretion either way: receivers "MAY choose to reject
or quarantine email even if email passes the DMARC mechanism check."
So the evidence here is neither the send call nor the 250. It is the sending
provider's own delivery record for that message, and then the mailbox.
AgencyProof's notification path opens a row in a delivery ledger before calling
the provider and finalises it as sent or failed afterwards. It never throws,
and returns false when no provider key is configured. The action above it
fails the form only when neither storage nor notification succeeded, so a
visitor can see a confirmation while the notification that would have told a
human never left. All of that is intentional, and it is exactly why the ledger
row is the artifact rather than the screen.
Enquiry notification never left the sending domain
FILED ON · /contact
Fixture run: the handler stored the enquiry and the page rendered its
confirmation. The delivery ledger recorded the same message as failed
against an unverified sender. Evidence: the ledger row and the provider's
rejection string. Nobody received the enquiry, and nothing on the visitor's
screen said so.
A notification path that goes nowhere is the archetypal launch blocker: high consequence, invisible to anyone looking at the site, settled by one observation. Whether it blocks a particular launch is a call somebody has to make, and agreeing that test before the findings arrive keeps it from becoming an argument.
Reply to the message you just sent
Open the notification, reply to it as if you were answering the lead, and confirm the reply arrived.
This is the stage everyone skips, and the one that loses an enquiry after it has
already been delivered. AgencyProof's notification sets a reply address only
when the submitted value matches a basic address pattern; otherwise a reply
goes back to the brand address that sent the alert. The same outcome arrives
elsewhere by other routes: an inbox rule that archives anything from the
sending domain, an alias that expands to somebody who left, a no-reply sender
a reply cannot reach. Thirty seconds of work, and the only way to establish
that the loop closes rather than merely starts.
Write the receipt while the tabs are open
Every field on the receipt is cheap now and expensive to reconstruct later.
- Form and page. Which form, at which URL, on which build reference.
- Run at. Date and time, with the timezone.
- Observed result. What you saw at each stage, in the words of the evidence
rather than a verdict:
303 to /thanks,record 8841 at 09:12:44Z,delivery record: sent. - Destination. Where the submission ended up, named exactly. The store and table, the CRM object, the mailbox address. Not "the team".
- Owner. A person, with a role, per destination. An unowned destination is the same as no destination.
- Not established. Spam placement for other recipients, behaviour under load, anything you could not sign in to check.
- Retest evidence. The date the trace runs again, and against which build. A receipt with no retest date describes a moment, not a system.
Fill in the evidence column of the trace table and you have the body of it.
Keep the file where the argument will happen
Three weeks after launch somebody says the form has never worked. What settles that conversation is not memory and not the confirmation screen.
| Question the client will ask | Settled by the success message | Settled by the trace | Settled by neither |
|---|---|---|---|
| Did the page respond? | Yes | Yes | |
| Did the request reach the server? | No | Yes | |
| Was my enquiry stored? | No | Yes | |
| Did anyone get a notification? | No | Yes | |
| Inbox rather than spam? | No | For the recipient tested | For every recipient, later |
| Can we reply to the sender? | No | Yes | |
| Was it working last Tuesday? | No | Only if run then |
The fourth column is the honest one. A trace establishes what happened on one run, for the recipients you tested, on the build you tested. It does not establish that the path still holds, which is why the retest date is a field rather than a nicety.
File the receipt where the argument will happen: with the launch record the client received, not in the tester's notes. A receipt somebody else can find without asking you is worth more than a thorough one only you know about.
Provenance
- Written by
- Balaji PonnambalamFounder, AgencyProof
- Technical review
- Written and self-reviewed against the codebaseAgencyProof is a one-person engineering team; there is no second reviewer to claim.
- Published
- Evidence basis
- Examples in this article are synthetic fixtures built for the explanation.
Every product claim in an article is checked against the AgencyProof source tree before publication, and traced to the code path that implements it. Where the article describes a method rather than a shipped behaviour, it says which it is.
Related reading
Launch ManualThe pre-launch website QA checklist agencies can actually use
A launch checklist short enough to execute and specific enough to verify — split into evidence, judgment, and the client-facing launch decision.
Agency Field NotesDecide what blocks a launch before the findings arrive
An automated severity label is an input to a launch decision. Agree the blocker test with the client before the findings arrive.
Launch ManualWhat to inspect before sending a website to the client
The best time to find a defect is before the client's attention shifts from the design to the mistake. A one-hour inspection for the first “finished” link.