How to build or fix contact forms and spam filtering with Claude Code
How to build or fix contact forms and spam filtering with Claude Code

Claude Code can build a contact form from scratch or fix one that is losing leads: the form itself, the server code that receives it, the email notification, a stored copy of every submission, the spam filtering and the success and error messages. The rules that keep it safe are simple. Every secret (an email service key, a CAPTCHA secret, a CRM token) stays on the server, never in code a browser downloads. Spam filtering is layered, imperfect, and should hold suspicious messages for review rather than delete them. And a person tests the whole route, from Submit to inbox, before it goes live.
Forms fail quietly. A page can say "Thanks, we'll be in touch" while the message goes nowhere, or a spam rule can bin real enquiries for weeks. This guide is built around catching those failures.
Key Takeaways
- Start with a read-only map of the current form: where it submits, where the notification goes, where submissions are stored, and what happens when something fails.
- Secrets belong in the host's environment variables, read by server code. Never put them in front-end JavaScript, in prompts, or in CLAUDE.md.
- CAPTCHA widgets don't protect a form on their own. Cloudflare says "The client-side widget alone does not protect your forms"; tokens must be verified by your server with your secret key.
- No spam filter is perfect. Every layer can block some real people, so hold flagged messages with a reason and review them, instead of silently discarding them.
- Store each submission before sending the notification email, so a failed email doesn't lose the lead.
- Test success, failure and spam paths end to end, and repeat a test submission on a schedule after launch.
How a contact form actually works
A form has three parts. The page shows fields and a button. When someone submits, the browser sends the data to an endpoint, a piece of server code such as a PHP script, a serverless function or your CMS's form plugin. That code checks the data, decides whether it's spam, stores it, and sends a notification by email or to a CRM. The page then shows a success or error message based on what the server replied.
The browser is the part anyone can see and change. Anything in the page's code, including keys, is public. That's why all checks that matter and all secrets live on the server.
Step 1: map the current form
Example prompt: Use plan mode. For every form on the site, tell me: the page it's on, where it submits, what server code handles it, where the notification is sent, whether submissions are stored anywhere, what spam checks run, and what the visitor sees if sending fails. Flag any API key, password or secret that appears in front-end code. Don't change anything.
Read the answer carefully. Common findings are a form that shows success before the server replies, a notification address belonging to someone who left, no stored copy of submissions, or a key sitting in a JavaScript file. A key found in front-end code should be treated as exposed: rotate it, then move the new one to the server.
A person reviews: the map, and confirms who should receive leads.
Step 2: decide what the form should do
These are business and privacy decisions, so make them before Claude Code writes code.
- Fields. Ask only for what you'll use. Every extra field is more data to protect.
- Recipients. Who gets the notification, and who is the backup.
- Storage and retention. Where submissions are kept, who can see them, and for how long.
- Privacy notice and consent. What your privacy policy says about form data. This depends on where you and your visitors are; it's a question for your counsel, and nothing here is legal advice.
- Spam policy. What happens to a message the filter flags: held for review, not deleted.
Step 3: build the server side, with secrets kept off the page
Example prompt: Build a server endpoint for the contact form. Validate every field on the server. Save each submission to storage before sending the notification email, and log the result of the send. Read the email service key from an environment variable; don't hard-code it and don't ask me for its value. Return a clear success or error response. Show me the plan first.
The key detail is the environment variable. You set the real value in your host's settings, and the code only reads its name. Claude Code never needs to see the secret, and it shouldn't. Add a deny rule such as Read(./.env) in your project settings so its file tools and common file commands can't open a local env file. Anthropic's permissions docs say these rules don't cover scripts that read files indirectly, so the stronger protection is never putting the real secret in the project at all. Our guide to keeping secrets away from Claude Code covers the full setup.
When we rebuilt eseospace.com and moved it off WordPress, Contact Form 7 and Popup Maker went with the old site. Claude Code wrote the replacement form function, and people approved every deploy. Our guide to rebuilding a website with Claude Code covers where forms fit in a migration.
Check: search the built site's front-end files for the key's name and any key-like strings. Nothing secret should appear.
Step 4: add spam filtering in layers
Spam filtering reduces junk. It doesn't eliminate it, and every rule can catch some real people. Use several light layers rather than one heavy one. The risk column below is our rule of thumb from building forms, not a measured rate.
| Layer | What it catches | Risk to real leads |
|---|---|---|
| Server-side validation | Missing fields, malformed emails, oversized input | Low, if the error message tells the visitor what to fix |
| Hidden field (honeypot) | Simple bots that fill every field | Some, since browser autofill can fill hidden fields. Flag, don't discard |
| Minimum time to submit | Bots that submit instantly | Low, if the threshold is a few seconds |
| Rate limiting by address or IP | Floods of repeat submissions | Low to moderate on shared networks |
| CAPTCHA (Turnstile, reCAPTCHA) | Many automated submissions | Moderate: friction for some visitors, failures if the token expires |
| Content rules (links, keywords) | Obvious sales spam | Higher: real messages can contain links or the same words |
If you add a CAPTCHA, verify it on the server. Cloudflare's Turnstile documentation says "You must call the Siteverify API to complete your Turnstile implementation," because "Tokens can be forged", each is valid for "300 seconds (5 minutes)", and each is single-use. The verification call uses your widget's secret key, so it happens only on the server. Google's reCAPTCHA documentation describes the same pattern: the token is verified with "the secret key" and "expires after two minutes". If a token expires before the form is sent, verification fails, so the form should get a fresh token and tell the visitor to try again rather than failing silently.
Example prompt: Add a honeypot field, a minimum submit time and Turnstile with server-side Siteverify. Don't delete anything a check flags: store it with status "held" and the reason, and send held messages to a separate daily summary instead of the main inbox.
Step 5: never drop a lead silently
The worst spam setup is one that throws away real enquiries without anyone knowing. Two habits prevent it:
- Hold, with a reason. Every flagged message is stored with the rule that flagged it. A person skims the held list on a schedule and releases real leads.
- Read the numbers. If held messages suddenly outnumber accepted ones, or accepted leads drop to zero, a rule is probably catching real people. Check before tightening anything.
Step 6: success, errors, tracking and accessibility
- Show success only after the server confirms the submission was stored.
- On failure, keep what the visitor typed, say what went wrong in plain words, and give another way to reach you, such as a phone number.
- Send a GA4 lead event only on confirmed success, with no personal data in it. Our guide to GA4 events and key events with Claude Code covers this.
- Give every field a visible label, make errors readable as text and not color alone, and make the button easy to tap. WCAG 2.2 sets a minimum target size of "at least 24 by 24 CSS pixels", with exceptions. Our guide to accessibility fixes with Claude Code explains what automated checks can and can't find.
Step 7: test end to end before launch
Run these on a staging copy, then once more on the live site after deploy.
| Test | Expected result |
|---|---|
| Real submission with test details | Stored, notification arrives in the right inbox (not its spam folder), success message shows |
| Submission with a required field empty | Clear error, typed data kept, nothing stored |
| Honeypot filled in | Stored as held with the reason, no main notification |
| Email service unavailable (simulated) | Submission still stored, error logged, visitor sees an honest message |
| Expired CAPTCHA token | Visitor is asked to try again, not silently rejected |
| Phone-width screen and keyboard only | Form usable, labels and errors readable |
Claude Code can run many of these through its Chrome integration, which drives a visible Chrome window. When it meets a CAPTCHA, it pauses and asks you to handle it manually, which is a good reminder that the human checks still matter.
A person reviews: the diff of the form code, especially anything touching secrets, and confirms the test results personally. Our article on whether Claude Code is safe for production websites covers why a person approves every deploy.
After launch: keep testing
Forms break when nobody is looking: a host changes, a key expires, an email provider tightens rules. Put a test submission on a schedule, such as weekly, and check that it arrives. Compare stored submissions with notifications received. Our website security checklist covers the wider housekeeping, such as rotating keys and limiting who can access stored submissions.
Conclusion
A reliable contact form is mostly server work: validate, store, notify, and filter spam in layers that hold rather than delete. Claude Code builds and tests that quickly. People decide the fields, recipients and privacy rules, keep the secrets in the host's settings, and confirm the test results. If you'd like a form built or repaired, see our Claude Code development services. More guides are in our Claude Code Guide.
Frequently asked questions
Can I use a form service instead of my own server code?
Yes. Hosted form services and CMS form plugins handle the server side for you. The same rules apply: keep any keys in the service's settings or the host's environment variables, check where submissions are stored, and test the full route.
Is a CAPTCHA enough to stop spam?
No single layer is enough. A CAPTCHA only works if your server verifies each token, and it adds friction for some visitors. Combine it with lighter checks and a held queue you review.
Can Claude Code see my form submissions?
Only if you give it access to where they're stored. Don't. Test with made-up details, and keep real submissions out of the repository, prompts and logs Claude Code reads.
Why do my form emails land in the spam folder?
Email deliverability depends on the sending service and your domain's email settings, not only the form. Send through a reputable email service using your own domain, and test delivery to the real recipient inbox after any change.
Does the CAPTCHA site key need to be secret too?
The widget runs in the browser, so whatever it needs to load is visible to visitors. The secret key is the one that must stay on the server, because it's what your server uses to verify tokens.
Put this into action with eSEOspace
We help businesses grow with seo, geo & aeo that actually performs. Explore the services behind this guide:
Get a FREE Audit
We'll perform a comprehensive SEO, AEO, GEO & CRO audit of your website — completely free — and show you exactly how to outrank your competitors.
Don't have a site yet? Get in touch →
Get a FREE GEO/AEO/SEO Audit
We'll analyze your site's SEO, GEO, AEO & CRO — completely free — and show you exactly how to get found across Google and AI answers.
Don't have a site yet? Get in touch →
Great — your audit is on the way!
We'll send your free SEO/GEO/AEO/CRO audit within the next few hours. Where should we send it?
You're all set! ✓
Your free audit is being prepared — check your inbox in the next few hours. Talk soon!
On this page
- Key Takeaways
- How a contact form actually works
- Step 1: map the current form
- Step 2: decide what the form should do
- Step 3: build the server side, with secrets kept off the page
- Step 4: add spam filtering in layers
- Step 5: never drop a lead silently
- Step 6: success, errors, tracking and accessibility
- Step 7: test end to end before launch
- After launch: keep testing
- Conclusion
- Frequently asked questions





