Skip to content

How to fix: Why is my website Contact Form Not Functioning?

When a contact form fails to function, it typically indicates a breakdown in the Client-Server Communication Loop. This is rarely a single “error” but rather a failure at one of four critical stages:

  1. Event Capture: The JavaScript listener fails to intercept the submit event.
  2. Payload Serialization: Data is incorrectly formatted (e.g., sending [object Object] instead of JSON).
  3. Transport Layer: The fetch or XMLHttpRequest fails due to CORS, incorrect endpoints, or network timeouts.
  4. Server-Side Processing: The backend receives the data but fails to interface with the Mail Transfer Agent (MTA) or database.
Symptom Common Cause Technical Layer
Page refreshes on submit Missing event.preventDefault() DOM / Event API
Form data is empty on server Incorrect Content-Type headers or missing name attributes HTTP Protocol
Access-Control-Allow-Origin error Cross-Origin Resource Sharing (CORS) mismatch Security/Infrastructure
405 Method Not Allowed Sending POST to a GET endpoint Routing
500 Internal Server Error SMTP authentication failure or environment variable misconfiguration Backend / MTA

The most common “silent” failure is the page refreshing before the JavaScript executes. You must explicitly cancel the default browser behavior.

const contactForm = document.getElementById('contact-form');
contactForm.addEventListener('submit', async (event) => {
// Stop the browser from refreshing the page
event.preventDefault();
const formData = new FormData(event.target);
const data = Object.fromEntries(formData.entries());
try {
await submitData(data);
} catch (err) {
console.error('Submission failed:', err);
}
});

If your server expects JSON but you send a FormData object without headers (or vice-versa), the body will be empty. Open the Network tab in F12 DevTools to inspect the request.

async function submitData(data) {
const response = await fetch('https://api.example.com/v1/send', {
method: 'POST',
headers: {
// Crucial: Tells the server to parse the body as JSON
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Server responded with ${response.status}: ${errorText}`);
}
return response.json();
}

If your frontend is on example.com and your API is on api.example.com, the browser will block the request unless the server explicitly allows it. In a Node.js/Express environment, use the cors middleware.

const express = require('express');
const cors = require('cors');
const app = express();
// Configure CORS to allow specific origin
app.use(cors({
origin: 'https://your-frontend-domain.com',
methods: ['POST'],
allowedHeaders: ['Content-Type']
}));
app.use(express.json());
app.post('/v1/send', (req, res) => {
const { email, message } = req.body;
if (!email || !message) {
return res.status(400).json({ error: 'Missing fields' });
}
// Proceed to send email via Nodemailer/SendGrid
});

Always check the browser console for specific status codes:

  • Press F12 or Cmd + Option + I.
  • Navigate to the Network tab.
  • Filter by Fetch/XHR.
  • Click on the failed request to view the Response body provided by the server.
  • Client-Side Validation: Use the required attribute and pattern regex in HTML to prevent junk data from hitting your API.
  • Rate Limiting: Implement a “Debounce” on the submit button or use a library like express-rate-limit to prevent spam.
  • Environment Variables: Never hardcode SMTP passwords or API keys in your JavaScript. Use .env files and access them on the server side.
  • Loading States: Always disable the <button type="submit"> once clicked to prevent race conditions and duplicate submissions.
  • Sentry/LogRocket: Use observability tools to capture 4xx and 5xx errors in production so you know the form is broken before your users tell you.