A contact form that "works" is not the same as a contact form that gets read. I have seen plenty of sites where the form shows a nice success message, the visitor leaves happy, and the email quietly lands in the owner's spam folder or never arrives at all. Nobody notices until a customer calls and asks why nobody replied to them last week.
I ran into this on my own portfolio site too. The form was sending, but the alerts kept ending up in spam. Fixing it properly took a few changes on the server side and a few in DNS. This is the setup I use now on the sites I build, and how I check that it actually works.
Why contact form emails go to spam
Email providers like Gmail and Outlook decide where a message goes based on who sent it, whether the sending server is allowed to send for that domain, and whether the message looks like the thousands of junk messages they block every day. Most contact form problems come from a handful of causes.
The site uses PHP mail()
Many PHP sites send form emails with the built-in mail() function. It hands the message to the web server's local mail program, which often sends it without proper authentication. To Gmail, it looks like an unknown server sending mail that claims to be from your domain. Sometimes it gets through, often it doesn't, and you get no error either way.
The From address is the visitor's email
A very common mistake is putting the visitor's address in the From field, so the email "comes from" someone@gmail.com. Your server is not allowed to send mail as gmail.com, and Gmail knows it. That message fails every check and is treated like a forged email, because technically it is one.
SPF, DKIM and DMARC are missing or wrong
These are three DNS records that tell other mail servers which servers can send for your domain and let them verify the message was not changed on the way. If any of them are missing, or there are two SPF records fighting each other, deliverability drops. In my case DKIM was already turned on, which is why I first assumed the setup was fine. The real problem was that mail() was not sending through the authenticated mailbox at all.
Bots are filling in the form
If bots send dozens of junk submissions a day, those messages come from your server and look exactly like spam, because they are. Over time that can hurt how your domain's mail is treated, and the real enquiries get buried in the noise.
How I set up a contact form now
Send through SMTP with a real mailbox
Instead of mail(), I send through SMTP using a real mailbox on the client's domain, something like hello@ or info@. The form logs in to that mailbox and sends the alert the same way an email app would. Because the message goes out through the domain's proper mail server, it gets signed and passes the checks.
In PHP I use PHPMailer for this. The SMTP password lives in a config file outside the public folder, never in the code that goes to Git.
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
$mail->isSMTP();
$mail->Host = $config['smtp_host'];
$mail->SMTPAuth = true;
$mail->Username = $config['smtp_user'];
$mail->Password = $config['smtp_pass'];
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;
$mail->setFrom($config['smtp_user'], 'Website enquiry');
$mail->addAddress($config['notify_to']);
$mail->addReplyTo($email, $name);
Put the visitor in Reply-To, not From
The email is sent from the site's own mailbox, and the visitor's address goes in Reply-To. The owner still just presses Reply and the answer goes straight to the customer. Nothing is forged, so nothing fails.
Check the DNS records
I log in to the domain's DNS and make sure there is exactly one SPF record that includes the mail provider, DKIM is enabled for the mailbox, and there is at least a basic DMARC record. Your hosting or email provider gives you the exact values. As an example of the shape only, they look something like this:
example.com TXT "v=spf1 include:_spf.your-mail-provider.com ~all"
_dmarc.example.com TXT "v=DMARC1; p=none; rua=mailto:you@example.com"
I start DMARC on p=none so nothing gets rejected while I check the reports, then tighten it later once I know all the domain's mail passes.
Keep the alert email plain
The notification itself does not need to be fancy. A clear subject like "New enquiry from Ahmed about a website", the visitor's name, email, phone and message, and the page they sent it from. I avoid heavy images, link shorteners and words in capitals. A calm, simple layout is easier for the owner to read on a phone and less likely to look like a promotion.
Stop bots before they send anything
I usually start without a puzzle captcha, because it annoys real visitors. Instead I add:
- A hidden honeypot field that people never see but bots often fill in
- A minimum time check, since a real person does not fill a form in one second
- A simple limit on how many submissions one IP can send in an hour
- Server-side validation of the email and required fields
If spam still gets through after that, I add a light, invisible check such as Cloudflare Turnstile.
Save every enquiry, not just the email
Even with a perfect setup, email can fail. A mailbox gets full, a password changes, the provider has a bad day. So every form I build also saves the submission in the database first, then sends the email. The owner can see all enquiries in their admin panel, and if an alert never arrives, the enquiry is still there.
On the visitor's side, once the form is sent, I hide the form and show a clear success message saying what happens next. That way they know it went through and don't submit it three more times.
How I test it before calling it done
Seeing "Thank you" on the screen proves nothing about the inbox. My testing goes like this:
- Submit the real form on the live site, like a visitor would, from a phone and a laptop.
- Check the owner's inbox, not just whether the email arrived somewhere, but whether it is in the main inbox.
- Open the message in Gmail, choose "Show original" and confirm SPF, DKIM and DMARC all say PASS.
- Send a test to a tool like mail-tester.com to catch anything I missed in DNS or content.
- Try a Gmail and an Outlook address, because they don't always agree.
- Press Reply on the alert and make sure it goes to the visitor's address.
If one of these fails, I fix it and run the whole list again. It takes maybe twenty minutes, and it is the difference between a form that looks fine and one you can rely on.
A quick checklist
- Send through SMTP with a mailbox on your own domain, not PHP mail()
- From is your mailbox, Reply-To is the visitor
- One SPF record, DKIM on, a basic DMARC record
- A plain, readable alert email
- Honeypot, timing and rate limits to block bots
- Every submission saved in the database
- Real test submissions checked in Gmail and Outlook
Final thoughts
A contact form is often the main way a small business gets new work from its website, so it is worth an hour to make sure it actually reaches someone. None of this is complicated, but it is easy to skip when everything looks fine on the screen.
If your form emails are going missing or landing in spam and you are not sure why, send me a message with your site address. I am happy to take a look and tell you what I think is causing it.
Back to Blogs