When a contact form stops sending email, the form usually works and something between it and your inbox doesn't. Check four places in this order: the form's own code, the browser, the server that sends the email, then the inbox. Most sites find the cause in the first two. The lasting fix is to store every lead before any email is sent, so a failed email never costs you one.
Key takeaways
- Send the form once with the browser's Network tab open. The response tells you whether the form or the email is broken.
- A field without a
nameattribute is never sent, whatever the visitor typed. - PHP's
mail()returningtrueonly means the server accepted the message, not that it arrived. - Gmail has required SPF or DKIM from every sender since February 2024, so mail from an unauthenticated server often lands in spam.
- Store the lead first and send the email second. Then a failed email costs you a minute, not a customer.
1. Check the form itself
On static sites, and on sites built with Claude Code, Cursor or Lovable, the fault is often in the form's HTML or script rather than the email.
- The action points to the wrong place. The form sends to the URL in its
actionattribute. A typo, a staging URL or an endpoint from an old version of the site sends every lead nowhere. - The method isn't POST. A form without
method="post"sends its fields in the address bar as a GET request, which many form endpoints don't accept. - Fields have no
name. Browsers only send fields that have anameattribute. Anidalone isn't enough. - A script stops the submit and never sends anything. A submit handler that calls
event.preventDefault()has to send the data itself, usually withfetch. If that part is missing or throws an error, the visitor sees a thank-you message and nothing leaves the page. - The form service doesn't know your domain. Many form backends only accept posts from sites you've allowed and answer anything else with an error, often a 403. A new domain, or a move from staging to the live domain, is the usual trigger.
- The service is waiting for you. Several form services hold the first messages until you click a confirmation link they emailed you. Look for that email, in spam too.
2. Watch one request in the browser
Open your browser's developer tools, switch to the Network tab and send the form once.
- No request appears: the problem is the form or its script (step 1).
- A 4xx or 5xx response: the form reached its endpoint and was refused, or the endpoint failed. The response body usually says why.
- A CORS error in the console: the endpoint refused a request from your domain, or its answer didn't allow your page to read it. Check that your domain is allowed.
- A 2xx response but no email: the form works. The problem is sending or delivery (steps 3 and 4).
3. Check the server that sends the email
If your own server sends the email, it's the next suspect.
- PHP
mail()reports a success that isn't one. PHP's documentation is blunt:mail()returnstruewhen the mail was "accepted for delivery", and that "does NOT mean the mail will actually reach the intended destination." Many shared hosts also restrictmail()because spammers abuse it. - There's no mail server at all. Serverless platforms such as Vercel, Netlify and Cloudflare don't give your code a mail server to hand messages to, and many cloud providers block outgoing connections on port 25. Code that expects a local mail server fails, sometimes without an error.
- The credentials changed. If you send through an email service, a revoked or mistyped API key fails every send. The service's activity log shows each attempt and its error.
- The logs know. On cPanel hosting, Email › Track Delivery shows what the server tried to send. On your own server, look in
/var/log/mail.log.
4. Check the inbox
If the server sent the email, the receiving side decides where it lands.
- Look in spam first, and search for the sender address rather than scrolling.
- Send a test to two different providers, for example a Gmail address and your business address. If one gets it and the other doesn't, the receiving side is filtering it.
- Check authentication. Since February 2024, Google has required every sender to Gmail to set up SPF or DKIM for their domain, and senders of more than 5,000 messages a day to set up SPF, DKIM and DMARC (Google's sender guidelines). In Gmail, open the message, choose Show original from the three-dot menu, and look for PASS next to SPF, DKIM and DMARC.
- Check the recipient address and inbox rules. A typo in the "send to" setting, or a filter that archives anything with "form" in the subject, looks exactly like a broken form.
Fix it for good: send through an email service
If your server is the problem, stop sending from it. Transactional email services send from your authenticated domain and log every message, so you can see what happened to each one.
| Service | Free plan (checked October 2026) | Good to know |
|---|---|---|
| Postmark | 100 emails a month, doesn't expire | Transactional email only |
| Resend | 3,000 emails a month, at most 100 a day | API first |
| Mailgun | 100 emails a day | One custom sending domain on the free plan |
| SendGrid | No free plan since May 2025; new accounts get a 60-day trial | Paid plans after the trial |
On WordPress, an SMTP plugin such as WP Mail SMTP sends the site's mail through one of these services. In your own code, use the service's library, or Nodemailer with SMTP in Node.js. Then send a test, and check the service's activity log as well as your inbox.
On a static or serverless site
Platforms like Vercel, Netlify and Cloudflare Pages host your pages but don't run a mail server for them. You have two options:
- Write a serverless function that receives the form and calls an email service's API. You get full control, and you maintain it: errors, retries, spam, and what happens when the email service is down.
- Point the form at a form backend. The form's
actiongoes to the backend's endpoint, and the backend stores the lead and tells you about it. No server code. Our guide to form backends for static sites compares the options and what each costs.
Store the lead first, then send the email
Email has too many places to fail to be the only copy of a lead: a blocked function, a revoked key, a spam filter, an inbox rule. Most owners find out weeks later, when someone mentions they wrote and never heard back.
The fix is to split capture from notification. Save the lead somewhere durable first, then send the email. If the email fails, the lead is still there to read, resend or pass on by webhook. The same thinking applies to visitors who never press send at all: our guide to form abandonment covers how to keep the ones who leave halfway, lawfully.
OllieLeads works this way: every lead is stored before your visitor sees a thank-you, and you hear about it by email, Slack or Discord within a minute. If an alert fails, the lead is still waiting in the app. The developer docs show the endpoint, forwarding from your own server, and the setup for coding agents.
FAQ
Why does my contact form send email only some of the time?
Intermittent failures usually come from rate limits on the sending server, a provider that rejects some messages from an unauthenticated domain, or a spam filter scoring each message differently. Sending through an email service from an authenticated domain makes delivery consistent, and its activity log shows which messages failed and why.
How do I know if the form's email is going to spam?
Send a test to a Gmail address and check Spam straight away. If it's there, open it, choose Show original and check that SPF, DKIM and DMARC say PASS. A FAIL means the sending domain isn't authenticated, which you fix with the DNS records your email service gives you.
My form works locally but not on the live site. Why?
Locally, a tool like Mailpit or MailHog often catches the mail, so the send looks successful. On the live site, check three things: the production environment has the email service's API key, your form service allows the live domain, and the form's action points to the production endpoint, not a local or staging one.
Can I get back leads sent while the form was broken?
Only if something stored them before the email failed. Check your form plugin's entries, your database, your email service's activity log and your server logs. If the form only ever sent email, those leads are gone, which is the case for storing every lead before notifying anyone.
Is there a way to skip email setup entirely?
Yes: point the form at a form backend. It receives each lead, stores it and sends the alerts from its own authenticated domain, so there's nothing to configure on your server. OllieLeads does this, and its remote MCP server lets Claude Code or Cursor connect the form for you.
Built with SuperDuperSEO: programmatic SEO content for SaaS, agencies and indie hackers