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 1,000 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.
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 — splitforms vs Formspree.