Your contact form is not sending email. Here is why
The frustrating part of this failure is that it looks identical from the outside no matter which layer broke. This guide works through the causes in the order that eliminates the most possibilities fastest, starting with the single check that tells you whether the problem is in the browser or on the server.
A contact form that does not send email is almost always one of five causes: the form has no backend receiving it, the sending address was never verified, the notification landed in spam, the SMTP credentials are wrong or expired, or the submission never left the browser because of a JavaScript or CORS error. Check the browser network tab first. It separates the last cause from the other four immediately.
The minimal integration
Before debugging your markup, post to your endpoint directly. If curl produces a submission and an email, the backend is healthy and the problem is in your page. If curl fails too, the problem is server-side and nothing you change in the form will help.
curl -X POST https://mtform.co/f/your-form-key \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"test@example.com","message":"Hello from cURL"}'Run in a terminal to verify the endpoint works. Prints the raw JSON response.
Step by step
- 01
Open the browser network tab and submit
Watch for the POST request. No request at all means a JavaScript error or a button that is not type="submit". A request with a red CORS error means the endpoint rejected your origin. A 2xx response means the submission was accepted and the problem is downstream in delivery.
- 02
Post to the endpoint directly with curl
This removes your page, your framework, and the browser from the equation entirely. A successful curl proves the backend receives and delivers; a failing one localises the fault to the server side.
- 03
Confirm the recipient address is verified
Mail providers will not deliver to an unverified destination, and most form backends refuse to send until you click the confirmation link. Submissions received before verification should still be stored, so check the stored payloads too.
- 04
Check spam, promotions, and any server-side rules
Automated notifications from a new sending domain are routinely filtered for the first few weeks. Search your whole mailbox for the sender rather than only looking at the inbox.
- 05
Verify the submission was stored regardless
Delivery and storage should be separate systems. If the payload is in your inbox but no email arrived, the form is working and the fault is purely in delivery, which is a much smaller problem than a lost submission.
Common pitfalls
A button without type="submit"
Inside a <form>, a <button> defaults to type="submit", but a <div> or an <a> styled to look like a button does not submit anything. If no network request fires at all, check this first.
Assuming no email means no submission
These are separate failures. A well-built form backend stores the payload the moment it is accepted, before attempting any notification, so the data is usually still there even when the email never arrives.
preventDefault() with no fetch call
A very common AI-generated pattern: the handler blocks the native submit and then never posts anywhere. The form appears to work, shows a success message, and sends nothing.
Sending from an address you do not control
Setting the From header to the visitor’s address fails SPF and DMARC and gets the message rejected or spam-foldered. The sender should be your verified domain, with the visitor in Reply-To.
Troubleshooting
| Symptom | What is happening | Fix |
|---|---|---|
| no request | Nothing appears in the network tab on submit. | The button is not submitting or a JavaScript error broke the handler. Check the console and confirm the button is type="submit" inside the form. |
| CORS | The request is blocked before it reaches the server. | Add the exact posting origin to your form’s allowed domains, including the protocol and both www and non-www variants. |
| 403 | The endpoint rejected the submission. | The origin is not allowed, or the form key is wrong. Compare the key in your markup against the one shown in the dashboard. |
| 422 | Accepted the request but rejected the payload. | A required field is missing or empty. Confirm every input has a name attribute. Inputs without one are never sent by the browser. |
| 200, no email | The submission is stored but no notification arrives. | The recipient is unverified, the notification is in spam, or the monthly quota is exhausted. All three leave the stored submission intact. |
Frequently asked questions
→Why is my contact form not sending emails?
The most common cause is that the form has no backend at all. HTML cannot send email on its own, so a form with no server behind it silently does nothing. The next most common causes are an unverified recipient address, the notification being filtered into spam, and a JavaScript handler that blocks the submit without posting anywhere.
→How do I know if my form submission was received?
Open the browser network tab and submit. A 2xx response means the server accepted it. If your form backend stores submissions, check the inbox directly. A stored payload with no email proves the form works and only delivery failed.
→My form worked before and stopped. What changed?
Most often an expired SMTP credential, a domain or DNS change that broke SPF and DMARC alignment, a deployment to a new origin that is not on the allowed list, or a monthly submission quota that has been reached.
→Can a form submission be lost?
It should not be. Storage and delivery are separate concerns, and a submission should be written durably the moment it is accepted, before any email is attempted. If your setup only emails and never stores, every delivery failure is permanent data loss.
Keep going
Last reviewed August 27, 2026
Ship this form for real
Create an endpoint, paste it in, and watch the first submission land in your inbox, in under three minutes.
Create free endpoint