Why "form to email" is harder than it looks
On paper, getting a contact form into your inbox is a one-liner. In practice, modern email is a hostile environment built to reject anything that smells weird. Your shiny new contact form has to navigate:
- SPF, DKIM, and increasingly DMARC checks at the receiving server
- Reputation scoring on the sending IP and domain
- Content scoring (links, attachments, suspicious phrasing)
- Greylisting, throttling, and silent drops by Gmail and Outlook
- Your own "Updates" or "Promotions" tab black holes
Skip any of these and your form will look like it's working — 200 OK on the request — while every submission silently lands in spam. The cost: lost leads you'll never know about. This is why we built splitforms in the first place: every other backend we tried treated deliverability as someone else's problem.
The 3 modern stacks that actually work
Forget PHP mail(). Forget unsigned SMTP from your VPS. Three patterns reliably deliver form submissions to a real inbox in 2026:
- Hosted form backend— splitforms, Formspree, Web3Forms, Basin. Drop a URL in your form's action.
- Serverless function + transactional email API — Vercel/Netlify/Workers calling Postmark, Resend, SendGrid, or Mailgun.
- Self-hosted forwarder — n8n, FormBricks, or a Docker container running on your own infra, sending through a paid SMTP relay.
Stack 1: Hosted form backend
The lowest-effort option. A form backend is a single POST endpoint that receives multipart form data, applies spam filtering, and emails the contents. With splitforms, the entire integration is one HTML file:
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="hidden" name="subject" value="New contact form submission" />
<input type="hidden" name="from_name" value="yoursite.com" />
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="What's up?" required></textarea>
<!-- honeypot -->
<input name="botcheck" type="checkbox" style="display:none" tabindex="-1" />
<button type="submit">Send</button>
</form>splitforms emails you the submission within ~2 seconds, sets Reply-To to the visitor's submitted email so you can reply directly, runs spam scoring before delivery, and keeps a copy in your dashboard so you don't lose data when an email bounces.
Pricing reality check: most hosted backends (including splitforms) have a free tier. We give 500 submissions/month forever; Formspree gives 50, Netlify Forms gives 100. Pick based on volume, not loyalty.
Stack 2: Serverless function + transactional API
You want full control or already have a Postmark/Resend account. Write a tiny function, ship submissions yourself.
Vercel Route Handler with Resend:
// app/api/contact/route.ts
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(req: Request) {
const data = await req.formData();
// honeypot
if (data.get('botcheck')) {
return new Response('OK', { status: 200 });
}
const name = String(data.get('name') ?? '');
const email = String(data.get('email') ?? '');
const message = String(data.get('message') ?? '');
const { error } = await resend.emails.send({
from: 'forms@yourdomain.com',
to: 'you@yourdomain.com',
replyTo: email,
subject: `New contact form: ${name}`,
text: `From: ${name} <${email}>\n\n${message}`,
});
if (error) {
return new Response('Mail send failed', { status: 500 });
}
return Response.redirect(new URL('/thanks', req.url), 303);
}You'll need to verify yourdomain.com in Resend by adding their SPF and DKIM CNAME records. Until those propagate, everything you send will land in spam.
Stack 3: Self-hosted forwarder + your own SMTP
Honest tradeoff: you save money at scale and own all the data. You also own deliverability, retries, queueing, monitoring, and the 3am pages when your VPS's IP gets blocked by Microsoft.
A minimal Node forwarder using nodemailer + Postmark SMTP:
import express from 'express';
import nodemailer from 'nodemailer';
const app = express();
app.use(express.urlencoded({ extended: true }));
const transport = nodemailer.createTransport({
host: 'smtp.postmarkapp.com',
port: 587,
auth: {
user: process.env.POSTMARK_TOKEN,
pass: process.env.POSTMARK_TOKEN,
},
});
app.post('/contact', async (req, res) => {
if (req.body.botcheck) return res.status(200).send('OK');
await transport.sendMail({
from: 'forms@yourdomain.com',
to: 'you@yourdomain.com',
replyTo: req.body.email,
subject: `Contact: ${req.body.name}`,
text: req.body.message,
});
res.redirect(303, '/thanks');
});
app.listen(3000);For most teams, the operational overhead beats the cost savings before you hit 10,000 submissions/month. After that, math starts flipping. We're honest about it: at very high volume, self-hosting plus a SendGrid contract is cheaper than splitforms.
SPF, DKIM, DMARC: the deliverability checklist
Three DNS records turn a contact form from "sometimes works" into "reliably hits the inbox":
splitforms sends from a verified sending domain we control, so you don't need to add any DNS records to start receiving mail. For deliverability-sensitive teams (sales, recruiting), connect your own domain in the dashboard and add the records once.
5 mistakes that send your forms to spam
- Sending from gmail.com or yahoo.com via SMTP relay. DMARC alignment fails; mail lands in spam or bounces. Use a domain you own.
- No SPF or DKIM record on the sending domain. Gmail will quietly drop these into spam.
- Putting the visitor's email in the From header. That's spoofing — DMARC will catch it. Put it in Reply-To instead.
- Including too many links in the email body. Spam scoring penalizes any email with 3+ outbound links.
- Sending HTML-only emails with no plain-text part. Plain-text fallback is a small but real spam-score win.
Tech support and troubleshooting
Five issues that account for almost every form-to-email outage:
- Mail lands in spam after working for weeks — domain reputation slipped or DKIM record was rotated. Re-verify SPF/DKIM and run a fresh mail-tester.com check.
- Reply hits the form provider, not the lead — Reply-To header isn't being set. Confirm your form has an
emailfield and the backend is configured to use it. - Submission returns 200 but no email arrives — message was scored as spam and silently dropped. Check the splitforms dashboard; resubmit after lowering spam threshold or whitelisting the IP.
- Bounces from your own domain — DMARC alignment failure when sending as user@yourdomain. Send as forms@yourdomain instead and put the visitor email in Reply-To.
- Latency over 10 seconds — shared SMTP host throttling or DNS lookup stall. Switch to a transactional provider (Postmark, Resend) or let splitforms handle delivery.
The full delivery pipeline, retry policy, and signature spec are in the splitforms docs and API reference; account questions live in the splitforms FAQ.
Frequently asked questions
What is the easiest way to receive HTML form submissions by email?
Use a hosted form backend like splitforms (https://splitforms.com/api/submit). Point your form's action attribute at the endpoint, include a hidden access_key input, and submissions arrive in your inbox immediately. No SMTP server, no DNS records, no PHP required.
Why does mailto: not work for contact forms?
mailto: opens the visitor's local email client, which mobile users on Gmail, anyone on a shared computer, or most corporate users won't have set up. Industry conversion data shows mailto links convert 3-10x worse than a real form. They also bypass any spam filtering or analytics.
Why are my form emails landing in spam?
Almost always a deliverability issue: missing SPF or DKIM records on the sending domain, sending from an unverified address, or using shared SMTP IPs with bad reputation. Use a dedicated transactional email service (Postmark, Resend, SendGrid) and authenticate your domain. splitforms handles this for you out of the box.
Can I receive form submissions to multiple email addresses?
Yes. splitforms supports CC and BCC recipients on Pro plans. You can also use a Google Group or shared inbox as the destination address. For per-form routing, create separate access keys per form, each pointed at a different recipient.
How fast do form-to-email submissions arrive?
With a modern transactional provider, end-to-end latency from form submit to inbox is typically 200ms-3 seconds. PHP mail() via shared hosting can take 30+ seconds or never arrive at all. splitforms ships emails through Postmark and averages under 2 seconds inbox time.
Troubleshooting: emails not arriving
You submitted a test message, the form returned 200, but no email arrived. This is the most common form-to-email frustration, and it almost always traces back to one of these root causes:
- Missing SPF record.Without SPF, the receiving mail server has no way to verify that the sending IP is authorized to send for your domain. Gmail and Outlook will silently route the message to spam or reject it entirely. Fix: add an SPF TXT record to your DNS that includes your email service's sending IPs.
- Missing DKIM record. DKIM adds a cryptographic signature to outgoing mail. Without it, a spammer could spoof your sending domain and mail servers would have no way to tell the difference. Most transactional providers generate the DKIM CNAME for you — you just paste it into your DNS.
- Wrong From address. If you set
From: visitor@gmail.com(the submitter's email), DMARC alignment fails because the sending domain (your email service) does not match the From domain (gmail.com). Always send from an address on your own verified domain and put the visitor's email in the Reply-To header. - Email landed in spam folder.Check your spam folder before assuming nothing arrived. Gmail, Outlook, and Apple Mail all have aggressive filters. If you find the email there, mark it as "not spam" — this trains the filter for future messages from that sender.
- DMARC policy blocking. If your domain has a strict DMARC policy (p=reject) and your SPF or DKIM fails, the receiving server will reject the message entirely. Start with
p=noneto monitor failures without blocking mail. - Greylisting delay. Some mail servers temporarily reject first-time senders and wait for a retry. Transactional providers handle retries automatically, but a self-hosted setup that does not retry will lose the message.
The fastest diagnostic tool is mail-tester.com: send a test email to the address it gives you and it scores your SPF, DKIM, DMARC, content, and IP reputation in one report. With splitforms, you skip all of this — the sending domain is verified and authenticated on our infrastructure before any email reaches your inbox.
Best practices for form-to-email
Beyond making email arrive, a few practices make form submissions actually useful for the humans reading them:
- Set a clear subject line.Include the form purpose — "New demo request from yoursite.com" is better than "Form submission" or the default mail() subject. Use a hidden
subjectfield in your form HTML. - Keep the email plain-text. Plain-text emails avoid rendering issues across email clients, pass fewer spam checks, and are faster to read on mobile. If you need formatting, send a multipart email with both text and HTML parts.
- Send auto-replies to the submitter.A brief confirmation email ("We received your message and will respond within 24 hours") sets expectations and reduces follow-up anxiety. On splitforms Pro, configure auto-replies in the dashboard without any code.
- Route by form, not by inbox. If you have multiple forms (contact, support, sales), use separate access keys with different email recipients. Your sales team should not see tech support tickets.
- Do not send notifications to a group address. Group inboxes create confusion about who is responsible for responding. Route to a named individual or a shared inbox with clear ownership.
- Forward to Slack, Discord, or Teams alongside email. Real-time chat notifications mean leads are seen in seconds, not hours. splitforms supports all three natively — Slack, Discord, and Teams.
The goal is that every submission is actionable within ten seconds of arriving. Clear subject, plain-text body, correct Reply-To, and a real-time notification channel get you there.
Next steps
- Add a Slack or Discord ping beside email — Slack or Discord.
- Persist every submission to a database — Google Sheets, Airtable, or Notion.
- Block bots before they reach your inbox — stop contact form spam.
- Compare splitforms with the alternatives — Formspree alternative and Web3Forms alternative.
- Need a starter form to point at email? Copy the free contact form template, or see Next.js form backend / React form backend.
- Get a free access key →