Your contact form works. The submission goes through, the visitor sees "thanks", and the email lands in spam, or nowhere. Most guides on this assume WordPress. This one is for any site: static pages, custom code, or a form service. It explains why it happens and how to fix it properly, starting with the most common cause.
Need it fixed in your own inbox today? In Gmail, open one of the form emails in Spam, click Not spam, then create a filter for the sender's address with Never send it to Spam. In Outlook, add the sender to Safe senders. That helps you, but not anyone else on your team, and it doesn't fix the cause. Read on for that.
Why form emails end up in spam
Mailbox providers like Gmail, Outlook and Yahoo decide where an email goes based mostly on two things: whether it is authenticated (provably sent by someone allowed to send for that domain) and whether its sender has a good reputation. Contact forms break the first rule in a few classic ways.
1. The email is "from" the visitor
This is the big one. It feels natural to set the From address to the person who filled in the form, so you can just hit Reply. But your server is not allowed to send email as priya@yahoo.com. To Yahoo, Gmail or Outlook, that email is indistinguishable from a forgery.
Many large providers publish a strict DMARC policy that tells everyone else to reject mail pretending to come from their domain. Yahoo and AOL have done this since 2014. So a form email with From: someone@yahoo.com, sent from your server, gets rejected or quarantined before you ever see it. The visitor did nothing wrong; the form did.
The fix: send from an address on a domain you control, and put the visitor's address in Reply-To. Your Reply button still goes to them.
From: Website form <forms@mail.yourdomain.com> Reply-To: Priya Nair <priya@yahoo.com> To: you@yourdomain.com Subject: New message from Priya Nair 2. The server isn't allowed to send for your domain
Even with the right From address, the email has to prove it really came from you. A plain PHP mail() call on shared hosting, or a script on a cloud server sending directly, usually proves nothing. Your domain has no record saying that server may send for it, so the email arrives "unauthenticated" and is treated with suspicion.
3. No DMARC, or nothing lines up
Passing SPF or DKIM isn't quite enough on its own. DMARC checks that the domain which passed matches the domain in the visible From address. If your email service signs with its own domain while your From says yourdomain.com, the checks can pass and still fail DMARC's "alignment" test.
4. The form is carrying spam
If bots fill your form with links and SEO pitches, every notification you send is, in effect, spam. Filters learn from that, and your real messages start sharing the same fate. Filtering junk before it is emailed protects your sending reputation as well as your inbox.
Get the headers right
Whatever sends the email, use these rules:
- From is always your domain, ideally a dedicated address like
forms@ornotifications@. - Reply-To is the visitor, so replying works as before.
- Subject is plain and predictable, such as "New message from Priya Nair". Avoid copying the visitor's text into the subject.
- Send plain text, or simple HTML with a plain-text part.
Here's what that looks like with Node.js and Nodemailer. Any email API (Resend, Postmark, Amazon SES, SendGrid) takes the same three fields.
import nodemailer from 'nodemailer'; const transport = nodemailer.createTransport({ /* your provider's SMTP settings */ }); // Never let newlines from the form reach an email header. const oneLine = s => String(s || '').replace(/[\r\n]+/g, ' ').trim().slice(0, 200); export async function notify({ name, email, message }) { const safeName = oneLine(name) || 'Website visitor'; const safeEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? oneLine(email) : undefined; await transport.sendMail({ from: 'Website form <forms@mail.yourdomain.com>', to: 'you@yourdomain.com', replyTo: safeEmail ? `${safeName} <${safeEmail}>` : undefined, subject: `New message from ${safeName}`, text: String(message || '').slice(0, 10000), }); } The oneLine step matters for security too: if a name containing a line break reaches a header, an attacker can add their own headers, including extra recipients. That's called header injection, and it's how contact forms get turned into spam relays.
Set up SPF, DKIM and DMARC
These are three DNS records that tell the world which servers may send email for your domain. Your email provider gives you the exact values; here's what each one does.
SPF: which servers may send
A TXT record on your domain listing the services allowed to send for it:
mail.yourdomain.com. TXT "v=spf1 include:spf.your-email-provider.com ~all" - A domain can have only one SPF record. If you send through several services, put all their
include:parts in the same record. - SPF allows at most 10 DNS lookups. Long chains of includes quietly break it.
DKIM: a signature on every email
Your provider signs each message with a private key, and you publish the public key in DNS, usually as a CNAME or TXT record at something like selector._domainkey.mail.yourdomain.com. Unlike SPF, a DKIM signature survives forwarding, which makes it the more reliable of the two.
DMARC: what to do when checks fail
_dmarc.yourdomain.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com" Start with p=none, which only collects reports, so you can see what's sending as you. Once everything legitimate passes, move to p=quarantine and later p=reject. That also stops others from forging your domain.
Use a subdomain for sending. Sending form notifications from
mail.yourdomain.comkeeps their reputation separate from your main domain, where your team's day-to-day email lives. If something goes wrong with one, the other isn't dragged down.
What Gmail, Yahoo and Outlook now require
Since 2024, Google and Yahoo have required every sender to pass at least SPF or DKIM. Senders of more than about 5,000 messages a day to their users must have SPF, DKIM and DMARC, keep their spam-complaint rate low, and offer one-click unsubscribe on marketing mail. Microsoft applied similar rules to high-volume senders to Outlook.com in 2025.
A contact form will rarely reach those volumes, but the baseline applies to everyone: unauthenticated email is increasingly likely to be filtered or refused outright.
How to check it's fixed
- Read the headers. In Gmail, open a form email, click the three dots, then Show original. You want
SPF: PASS,DKIM: PASSandDMARC: PASS. Outlook shows the same information under View message source. - Send a test to a scoring tool. Services like mail-tester.com give you a temporary address and a score with specific problems listed. Submit your form with that address as the recipient.
- Test several providers. Send to a Gmail, an Outlook and a Yahoo address. They filter differently.
- Watch over time. If you send a lot of mail from the domain, Google Postmaster Tools shows your reputation and spam rate.
Stop junk before it's emailed
The less spam your form forwards, the better your notifications are treated. A hidden honeypot field, a minimum fill time, link limits and per-IP rate limits catch most of it without bothering real visitors. There's a full walkthrough with code in how to stop contact form spam without a CAPTCHA. Keep flagged messages in a spam folder instead of emailing them.
If you use a form service
If a form backend sends your notifications, most of the above is its job, but it's worth checking two things:
- Emails arrive from the service's own authenticated domain, or from yours after you've added its DNS records, and never "from" the visitor. Use Show original to confirm all three checks pass.
- The visitor's address is in Reply-To, so replying still works.
Formpit, which I build, works this way: notifications come from its own authenticated domain with the visitor in Reply-To, spam is filtered before anything is emailed, and you can also reply from an inbox where their answer comes back into the same conversation.
Checklist
- From is your domain, never the visitor's. The visitor goes in Reply-To.
- Strip line breaks from anything that goes into a header.
- Send through a real email provider, not bare
mail(). - Publish SPF (one record), DKIM and DMARC for the sending domain, ideally a subdomain.
- Check Show original for three PASSes, and test Gmail, Outlook and Yahoo.
- Filter spam before it's emailed.
Fix the From address and add the three DNS records, and the large majority of "my form emails go to spam" problems go away.