A form submission has two legs — yours failed on the second
Every contact form is two pipelines glued together:
- The HTTP leg: browser → POST → server returns 200 → visitor sees "Thanks!"
- The email leg: server → SMTP → your mail provider → spam filtering → your inbox.
The success message only ever testifies about leg 1. Leg 2 happens after the response is already sent, server-to-server, with its own failure modes — and crucially, its failures are invisible to the visitor and usually invisible to you. An SMTP rejection doesn't un-show the thank-you page.
So when someone says "the form works but I get no email," the debugging never starts in the HTML. It starts at the seam between the two legs: did the server attempt the send, and what happened to it? (If the form itself is failing — errors, dead buttons, 4xx responses — that's the form debugging guide instead.)
Cause 1: it arrived — in spam (check this first)
Roughly half of all "no email" reports end here. Before touching any configuration:
- Search all mail, not the inbox: in Gmail,
in:anywhere subject:(form OR submission OR contact). - Check the spam folder and (Gmail) the Promotions/Updates tabs.
- On Google Workspace / Microsoft 365: ask your admin to check the quarantine — messages held there never appear in any folder you can see.
If you find the notifications in spam, stop here and follow the dedicated fix: why contact form emails go to spam — it's an SPF/DKIM/DMARC alignment problem, not a sending problem. The rest of this post is about emails that truly never arrived.
Cause 2: the recipient address is wrong, stale, or unverified
Embarrassingly common and therefore worth 60 seconds: open your form backend's settings (or your server code) and read the destination address character by character. The classics:
- A typo'd domain (
gamil.com,yourcompany.coinstead of.com). - The address of a developer or ex-employee who set the form up years ago.
- An unverified recipient: many services (and AWS SES in sandbox mode) require you to click a confirmation link before they'll send to an address. The confirmation email itself often sits unnoticed in spam, and every notification since has been silently dropped.
- An alias or group address (
info@,sales@) whose forwarding rule was removed when someone left.
Test by temporarily switching the recipient to a personal Gmail you control. If notifications arrive there, the pipeline is healthy and the problem is the original mailbox or its forwarding.
Cause 3: a quota cut you off mid-month
Every layer of the pipeline has a ceiling, and most fail closed and quiet:
- Form backend submission caps — free tiers commonly stop processing or stop emailing after a monthly cap (Netlify Forms: 100/month; others vary — splitforms' free plan is 500/month and the dashboard shows your usage).
- SMTP daily limits — free Gmail SMTP caps around 500 messages/day, Workspace at 2,000/day; exceed it and sends fail until the window resets.
- Provider suspensions — SES pauses sending if your bounce or complaint rate spikes; the notice goes to the AWS account email, which is often nobody's inbox.
The signature of a quota problem is temporal: everything worked until the Nth of the month or until a traffic spike (often a spam flood — see stopping contact form spam, since bot submissions burn quota too). Check the usage page of every service in your chain.
Cause 4: the send failed and the error was swallowed
If you run your own form handler, this pattern loses mail every day:
try {
await transporter.sendMail({ to: OWNER, ... });
} catch (err) {
// TODO: handle later
}
return Response.json({ ok: true }); // visitor sees success regardlessAuth failures (rotated app password), connection timeouts (host blocks outbound port 25/587 — most clouds do by default), and TLS misconfigurations all land in that empty catch. At minimum, log the error and the message ID on success; better, persist the submission before attempting the email, so a mail failure can't lose the lead. Serverless adds a sharper trap: returning the response before await-ing the send lets the platform freeze the function mid-SMTP-handshake — the send sometimes completes, which is the most confusing bug of all.
Cause 5: it bounced — and the bounce went somewhere you don't read
SMTP failures after acceptance produce a bounce message sent to the envelope sender (the Return-Path), not to you. If that's noreply@ or an unmonitored service address, delivery failures are structurally invisible.
Bounce categories worth knowing when you do find the log:
- 550 5.1.1 (user unknown) — recipient address doesn't exist. See cause 2.
- 552 (mailbox full) — yes, still happens, especially on legacy POP mailboxes.
- 554 / 5.7.x (policy rejection) — your message was refused for authentication or reputation reasons; this is the DMARC/blocklist family.
- 421 / 4.x.x (temporary) — greylisting or throttling; a good sender retries for hours, a naive script gives up immediately.
Use a sending path that surfaces bounces in a UI — SES, Mailgun, and Postmark all log every message's fate, and a form backend dashboard does the same one level higher.
Causes 6 & 7: inbox-side filters and forwarding chains
When the sending log says delivered but you see nothing, the message died inside your own mail system:
- Filters and rules — a Gmail filter ("skip inbox"), an Outlook sweep rule, or a "block sender" clicked by accident months ago. Audit: Gmail → Settings → Filters and Blocked Addresses.
- Forwarding chains —
info@yourdomain.comforwards to Gmail; the forward re-sends the message, breaking SPF on the second hop, and Gmail's filter eats it even though delivery toinfo@succeeded. The more hops, the more likely. Test each hop independently. - Gateway security products — Mimecast, Proofpoint, and Microsoft Defender quarantine without a user-visible trace. Corporate recipients: always check quarantine with an admin.
The 5-minute isolation procedure
- Submit a test from the live site. Confirm the backend received it (dashboard / database / server log). Not received → it's an HTTP-leg problem, different post.
- Search all mail + spam + quarantine for the notification.
- Open the sending log. No entry → quota or swallowed error (causes 3–4). Bounced → read the SMTP code (cause 5). Delivered → inbox-side (causes 6–7).
- Swap the recipient to a fresh personal Gmail and resend. Arrives → original mailbox/forwarding is the problem. Doesn't → sender side.
- Send a copy to
mail-tester.comfor a deliverability score; under 9/10, fix what it lists.
And make the next failure loud instead of silent: splitforms stores every submission in the dashboard before any email is attempted, so the inbox is a notification channel rather than the system of record — a delivery failure shows up as a visible gap instead of a lost lead. Add a webhook or Slack notification as a second channel and a mail outage costs you nothing. Free for 500 submissions/month; see receiving form submissions by email for the pipeline details.
FAQ
My form shows a success message but I never get the email. Why?
The success message only confirms the HTTP leg — the browser's POST reached a server that returned 200. The email leg is a completely separate operation that happens afterward, server-to-server, and its failures are invisible to the visitor. The five usual causes, in order of frequency: the email landed in spam, the recipient address in your settings is wrong or unverified, the sending quota was exhausted, the SMTP send failed silently (often with the error swallowed by a try/catch), or your inbox provider rejected the message at the gateway with no bounce you can see.
How do I tell whether the email was sent but lost, or never sent at all?
Check the layer between form and inbox: the sending log. Every serious email path has one — your form backend's delivery log, AWS SES's sending statistics, Mailgun and Postmark's message logs, or your server's mail logs if you run SMTP yourself. If the log shows 'delivered', the message reached your mail provider and the problem is inbox-side (spam folder, filters, forwarding rules). If it shows 'bounced' or 'rejected', read the SMTP error code in the log. If there's no log entry at all, the send was never attempted — look for an application error or quota cutoff.
What is a silent bounce and how do I see it?
When a mail server accepts your message and later determines it can't deliver it, it sends a bounce notice — but the bounce goes to the envelope sender (Return-Path), not to you, the intended recipient. If the envelope sender is an unmonitored noreply@ address or your form library discards async bounces, the failure is invisible: the sender thinks it delivered, you receive nothing. Fix: use a sending service that surfaces bounces in a dashboard (SES, Mailgun, Postmark all do), or set the bounce address to a mailbox someone reads.
Why did my form emails stop arriving when nothing changed?
Something changed — just not in your code. The usual suspects: your monthly sending or submission quota reset cycle was hit (free tiers cut off silently), your domain's DNS was migrated and the SPF/DKIM records didn't move with it, your mail provider tightened filtering (Gmail and Yahoo enforce authentication much more aggressively since 2024), the destination mailbox filled up, or a recipient marked an earlier notification as spam and trained the filter against you. Check the sending log for the date of the last successful delivery and correlate with billing cycles and DNS changes.
Does using my own email address in the form's From field break delivery?
Frequently, yes — and in a sneaky way. If the notification's From is the same address as the To (you@yourdomain.com sending to you@yourdomain.com) through a third-party server, DMARC sees a message claiming to be from your domain sent by a server your domain never authorized. Strict providers (Gmail with an enforced DMARC policy, Microsoft 365 defaults) reject or quarantine it. The correct pattern: From is an address on the sender's authenticated domain, Reply-To is the visitor. See our SPF/DKIM/DMARC guide for the full setup.
How do I test email delivery without spamming my real inbox?
Three tools: (1) mail-tester.com gives you a one-time address and scores the message's deliverability out of 10; (2) a disposable Gmail alias (you+formtest@gmail.com) lets you filter test traffic; (3) your form backend's dashboard shows the submission and the email's delivery status without any inbox at all. For staging environments, point notifications at a mailbox you own but never read from, so test sends can't train spam filters against your real address.
How does splitforms make this failure visible instead of silent?
Every submission is stored in the dashboard independently of email delivery, so data is never lost to a mail failure — the inbox is a notification channel, not the database. Delivery problems therefore show up as a visible gap: submissions present in the dashboard with no matching email tells you instantly the email leg is failing, and you still have every lead. You can also add webhook delivery to Slack or your own endpoint as a second notification channel, so a mail outage doesn't mean missed leads.
Stop losing leads to silent email failures: try splitforms free — every submission is stored in a dashboard first, so a missed email is never a lost lead.
Related: fix emails landing in spam, test a contact form before launch, receive submissions in Gmail, and the splitforms docs.