Every contact form I have ever owned has lost leads silently. The bugs do not crash. They do not show errors. They return 200 OK and drop the message into a hole. Last month I audited five production setups, including my own. Here are the seven failure modes I found, ranked by how many real submissions they swallowed before anyone noticed.
The from address gets soft-bounced into spam
The form sends notifications from no-reply@yourdomain.com. Gmail's algorithm sees that pattern and routes the email to spam, sometimes to a folder you never check. The submission exists in your database. You never get the notification. Three weeks later you find six leads you forgot to respond to.
How to detect: Search your spam folder for "contact form" or "new submission". If anything is there, you have this bug.
Fix: Use a real from address like forms@yourdomain.com, not no-reply@. Set up SPF, DKIM, and DMARC properly. Send yourself a test submission and verify it lands in the inbox, not promotions, not spam.
Reply-To is missing, so replies bounce
You get a notification. You hit reply. The reply goes back to forms@yourdomain.com instead of the actual lead. They never hear from you. They go to a competitor.
This bug is invisible from your side. The reply leaves your outbox. You feel like you handled the lead. Two weeks later the lead has signed with someone else.
How to detect: Reply to one of your own form notifications. Check whether the recipient is your form sender address or the actual submitter.
Fix: Always set Reply-To to the submitter's email when sending notifications. Sanitize it first (strip newlines, validate format) to avoid header injection.
The serverless function times out at 5 seconds
Your form is wired through a Vercel function. The function does: validate, save to database, send email, send Slack notification, render confirmation. Each step takes 1.5 seconds. The function times out before the email or Slack call lands. The submission is in the database, but you never got pinged.
How to detect: Check your function logs for timeout errors. Look for submissions in the database that don't have a corresponding notification log entry.
Fix: Move slow operations (email, webhook, Slack) into a background queue or fire-and-forget pattern. Return success to the user immediately after the database write. Process notifications in a worker, not in the request handler.
The email API rate-limited you silently
Most email providers cap free tiers at 100 sends a day. You launch a campaign, get 200 submissions, the email API starts returning 429 on submission 101. Submissions keep saving. Notifications stop. You only notice when a customer asks "did you get my message?" three days later.
How to detect: Check the response code from your email API call. If you ignore it (most code does), you have this bug.
Fix: Log every notification send. Set up an alert when the email API returns non-2xx. Better: queue notifications and retry on failure rather than dropping them.
Double-click on submit creates duplicate submissions, one of which fails
User clicks submit. The button doesn't disable. They click again 200ms later. Two requests fire. The first creates the database row. The second hits a unique constraint and 500s. The user sees the error message and assumes the form is broken. They leave.
The first submission did save. You got one notification. The user thinks it failed and emails you separately. You think they got the message twice. Confusion.
How to detect: Open your form, click submit twice quickly, check the network tab. Are both requests sent? Does the second one error?
Fix: Disable the submit button on click. Server-side, deduplicate by submitter email + timestamp window (e.g., same email in 10 seconds = treat as one).
Privacy browsers strip the honeypot timestamp, get treated as bots
Your spam filter checks a hidden timestamp field to detect bots that POST too fast. Brave, Firefox with strict tracking protection, and Tor Browser strip or zero out hidden form fields they don't recognize. Real users on those browsers fail the timing check and get dropped.
I found this one because my own dev account uses Brave. I submitted a test form, got a fake-success response, never saw it in the inbox. Took two days to figure out the browser was the culprit.
How to detect: Submit your form from Brave, Firefox with strict tracking, and a fresh Tor session. Check whether the submission appears.
Fix: If the timestamp is missing or parses to zero or negative, skip the timing check. Don't fail closed. Fall back to honeypot fields and rate limit alone.
HTML or unicode in the submitter's data breaks the email body
Someone fills the form with raw HTML tags in their name field. Your notification email renders that as literal HTML. The body fails to render in Gmail. You see "(no content)" or a broken layout. The actual message is hidden.
Less spectacularly: an emoji or CJK character in the message breaks the encoding header, Gmail rejects the email, you get a bounce hours later.
How to detect: Submit your form with the name field set to a string containing HTML tags. Check whether the notification email renders bold or shows escaped text. The latter is what you want.
Fix: HTML-escape every user-supplied value before injecting it into the email template. Set the Content-Type header explicitly to text/plain or text/html with charset utf-8. Test with non-Latin characters and emoji.
How many of these were silently happening to me
When I audited my own production setup six months ago, I had three of these seven bugs running quietly. The biggest one was #4 (Resend rate limit). I had been losing notification emails for two weeks during a small traffic spike. Customers thought I was ignoring them. I thought I had no leads. The database told a different story when I finally looked.
The fix that mattered most was not a single bug. It was logging every notification attempt with a status, so the next time something silently failed, I could see it instead of trusting that 200 OK actually meant the message reached me.
What I do now
I run a contact form on every site I ship. Each one goes through a setup checklist that includes:
Send a test submission from at least three browsers including Brave
Verify the notification lands in the inbox, not promotions
Reply to the notification and confirm the reply reaches the submitter
Wait until the submission count exceeds the free tier limit, then test again
Log every notification API call with status, retry failures, alert on consecutive errors
After getting tired of building this same setup five times, I open-sourced the form backend. The repo is at . It handles all seven of the above failure modes by default, so if you want to skip the homework you can self-host it or use the hosted version.
What is the silent failure mode that bit you hardest? Curious which of these you have hit, and which I missed.