splitforms.com
All articles/ GUIDES9 MIN READPublished May 1, 2026

Contact form not working? 8 common causes (and fixes)

From CORS errors to silent SMTP failures, the most common reasons a contact form silently breaks — and how to debug each in under 5 minutes.

✶ Written by
splitforms.com / blog

Founder of splitforms — the form backend API for developers. Writes about form UX, anti-spam, and shipping web apps without backend code.

First: 60-second triage flowchart

Before reading any of the eight causes, do this in 60 seconds:

  1. Open your form's page in a fresh browser tab and submit a test entry with a real email address.
  2. Open DevTools → Network. Submit again. Was a request fired? If no, jump to cause #6 (JavaScript).
  3. Read the response status. 200 OK? The backend received it — the failure is on the email leg (causes 1, 5, 7). 4xx?Read the response body — it'll usually name the issue (causes 2, 3, 4). 5xx?Backend problem — check the form backend's status page.
  4. Check the form backend's dashboard. Submission showing? Email-leg failure. Not showing? Frontend or routing failure.

That sequence narrows the bug to one of the eight causes below within a minute.

1. Emails are landing in spam

By far the most common failure. The form works, the dashboard shows submissions, the "email sent" log is green — but nothing arrives in your inbox.

Diagnose: search your spam/junk folder for the sender domain (e.g., splitforms.comor your custom sending domain). If it's there, that's your bug.

Fix:

  • Mark a few of the spam-folder messages as "Not Spam" in Gmail/Outlook to retrain the filter.
  • If you're sending from your own domain, verify SPF and DKIM records are published (see cause #5).
  • Add the sender address to your contacts.
  • Set up an inbox filter that auto-labels submissions and skips spam scoring.

splitforms surfaces "delivered to spam" in the dashboard when the receiving server reports it via DMARC aggregate reports — most other backends don't, which is why this bug hides for weeks.

2. Missing or wrong access key

The endpoint returns 401 Unauthorized or a JSON error like { "error": "invalid access_key" }.

Diagnose: View source on your live page and search for access_key. Confirm it matches what's in your form backend's dashboard. Common mistakes: pasted from the wrong account, copied with a leading space, or left as YOUR_ACCESS_KEY placeholder.

Fix:

<form action="https://splitforms.com/api/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
  <!-- replace YOUR_ACCESS_KEY with the real key from
       https://splitforms.com/dashboard -->
</form>

If the key is right but you're still getting 401, check whether you've enabled domain-locking in the dashboard — the key may be locked to a different origin than the one you're testing from.

3. CORS blocked the request

You see a console error like Access to fetch at '...' from origin '...' has been blocked by CORS policy.

Diagnose:CORS only fires on cross-origin fetch/XHR (not native form POSTs). If you're using fetch(), the receiving server isn't returning Access-Control-Allow-Origin.

Fix:

  • If using splitforms, double-check the URL is exactly https://splitforms.com/api/submit. We return CORS headers for any origin.
  • If using your own serverless function, return Access-Control-Allow-Origin: * (or your origin) and handle the preflight OPTIONS request.
  • Quick workaround: switch from fetch to a native form POST with action + a redirect — no preflight, no CORS.
// In a Cloudflare Worker / Vercel function:
return new Response(body, {
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
  },
});

4. Wrong Content-Type header

You're POSTing JSON to an endpoint that expects form-encoded data, or vice versa.

Diagnose: Check the request's Content-Type in DevTools. Most form backends expect either multipart/form-data (default for HTML forms) or application/json. Sending the wrong one usually returns 400 with "invalid request body".

Fix: When using fetch with FormData, don't set Content-Type manually — the browser sets it correctly with the multipart boundary:

// CORRECT — let the browser set Content-Type
const formData = new FormData(form);
await fetch(URL, { method: 'POST', body: formData });

// WRONG — manually setting Content-Type breaks multipart boundary
await fetch(URL, {
  method: 'POST',
  headers: { 'Content-Type': 'multipart/form-data' }, // breaks
  body: formData,
});

// CORRECT for JSON
await fetch(URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(data),
});

splitforms accepts both form-encoded and JSON. If you're using a different backend, check their docs for the expected format.

5. SPF, DKIM, or DMARC misconfigured

The form backend "sent" the email but Gmail/Outlook rejected or quarantined it because the sending domain isn't authenticated.

Diagnose: Send a test submission to a fresh address at mail-tester.com. The score (out of 10) tells you exactly which records are missing. Anything under 8 will start landing in spam.

Fix:

  • Add an SPF TXT record listing your sending provider's IPs.
  • Add a DKIM TXT/CNAME record (your provider gives you the value).
  • Add a DMARC TXT record starting with v=DMARC1; p=none for monitoring, then escalate to p=quarantine after a week of clean reports.

splitforms sends from a verified sending domain we own, so by default you don't need any DNS records to start receiving mail. For high-deliverability use cases (sales, recruiting), connect your own domain in the dashboard.

6. JavaScript error swallowed the submit

You bound a submit handler that calls e.preventDefault() and then a downstream error kills the rest. The form looks like it submits but no network request fires.

Diagnose: Open DevTools Console. Submit. Any red errors? Check the Network tab — was a request fired? If no request and no error, you have a silent failure (look for try/catch blocks that swallow exceptions).

Fix:

// BAD — silent failure if anything throws
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  try {
    await fetch(URL, { method: 'POST', body: new FormData(form) });
  } catch {} // <- this swallows everything
});

// GOOD — log errors and tell the user
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  try {
    const res = await fetch(URL, { method: 'POST', body: new FormData(form) });
    if (!res.ok) throw new Error('Status ' + res.status);
  } catch (err) {
    console.error('Form submit failed:', err);
    alert('Could not send. Please email us directly.');
  }
});

Fastest workaround if you're stuck: remove the JavaScript entirely and use a native form POST with action="https://splitforms.com/api/submit". The browser handles everything.

7. Free-tier quota exhausted

Your form was working all month, then stopped on the 28th. You've hit the free-tier limit on whichever backend you're using.

Diagnose:Check the dashboard's usage counter. Common limits in 2026:

  • splitforms free: 1,000 submissions/month
  • Formspree free: 50 submissions/month
  • Web3Forms free: 250 submissions/month (per access key)
  • Netlify Forms free: 100 submissions/month
  • Basin free: 100 submissions/month

Fix:Upgrade to a paid plan, or split traffic across multiple access keys (some teams do this; we don't recommend it as a long-term solution).

8. CDN or framework caching the POST

Edge cache or framework optimization decided your POST endpoint was cacheable and is now serving a stale response without ever hitting the origin.

Diagnose: Check the response headers in DevTools for cf-cache-status: HIT, x-vercel-cache: HIT, or similar. If you see HIT on a POST, you've found it.

Fix:

  • Add Cache-Control: no-store to the response.
  • In Next.js 16, ensure your route handler isn't accidentally being statically optimized. Add export const dynamic = 'force-dynamic' if needed.
  • In Cloudflare, exclude the endpoint path from page caching.

splitforms' endpoint sets Cache-Control: no-store by default, but if you're proxying it through your own domain via a CDN rewrite, you may need to override the CDN's default cache rules.

How to prevent this from happening again

  • Set up an uptime monitor on your form endpoint. A simple cron that POSTs a known payload every hour and checks for 200 OK catches most failures within an hour instead of weeks.
  • Use a backend with a delivery dashboard. You can't debug what you can't see. splitforms shows you every submission with delivery status, retry attempts, and spam-score reasons.
  • Forward submissions to a fallback inbox. Set CC or BCC to a second email so a single inbox issue doesn't cost you leads.
  • Test the form monthly. Add a calendar reminder to submit a real test entry and confirm end-to-end delivery.
  • Watch your DMARC reports.Free aggregators (Postmark's DMARC monitoring, dmarcian) email you a weekly summary of who's receiving, rejecting, or quarantining your mail.

Where to go next and how to get help

Frequently asked questions

Why is my contact form not sending emails?

The most common cause is silent SMTP failure: emails are sent but land in spam due to missing SPF/DKIM records. Check your spam folder first. If they're not there either, verify your form's POST endpoint is returning 200 OK in the browser's Network tab, and check your form backend's dashboard for delivery logs.

My form returns CORS errors. What do I do?

CORS errors mean your form's origin (e.g., yoursite.com) isn't allowed by the receiving endpoint. If you control the endpoint, add Access-Control-Allow-Origin headers. If you use a form backend like splitforms, the endpoint already returns CORS headers for any origin — the error usually means you're posting to the wrong URL. Double-check the action attribute.

My contact form was working but suddenly stopped. What changed?

Three usual suspects: (1) your form backend's free quota was exceeded for the month, (2) the recipient email's domain added strict DMARC and now rejects unauthenticated mail, or (3) a CDN or framework upgrade started caching the POST response. Check the form backend dashboard, recent DNS changes, and your CDN's POST handling rules.

Form submissions show up in dashboard but no email arrives. Why?

Submission was received successfully — the issue is on the email-delivery leg. Check your spam folder, verify your destination address in the dashboard isn't typo'd, look for bounce notifications, and check whether your inbox provider added the sender domain to a block list. splitforms surfaces all delivery failures in the dashboard.

How do I debug a form that isn't submitting at all?

Open the browser's DevTools Network tab, submit the form, and inspect the request. No request at all? JavaScript is blocking submit (preventDefault but no fetch). Request fires but errors? Read the response body and status code. Request returns 200 but nothing happens? Check whether the form backend received it via the dashboard.

What's the fastest way to test a contact form is working end-to-end?

Submit a test entry from the live site (not localhost), check that the visitor lands on your thank-you page, confirm the email arrives in your inbox within 30 seconds, and verify the entry appears in the form backend's dashboard. If all four happen, you're good. If any fails, the failure point tells you where to look.

Where can I get human help if none of the eight fixes work?

Open the dashboard's Submissions tab and copy the submission ID for any failing attempt. Email support@splitforms.com (Pro/4-Year priority) with the ID and a link to the page hosting the form. The /faq covers most common issues; /docs and /api-reference cover the request contract.

About the author
✻ ✻ ✻

Get your free contact form API key in 60 seconds.

1,000 free form submissions per month. No credit card. No SDK, no PHP, no plugin. Drop one POST endpoint in your form and submissions land in your inbox.

Generate access key →Read the docs
v0.1 · founders pricing locked in · early access open