Engineering

Stop building your own form backend. Just don't.

June 2, 20268 min read
Stop building your own form backend

The decision usually starts with a single thought: "I need to handle form submissions. I'll write a quick API route." That sounds like an afternoon of work, maybe less.

Then production happens. CORS errors that didn't exist on localhost. Bots that find the endpoint within hours of launch. An email notification that silently fails. A user who follows up two weeks later because they never heard back. A new spam wave that gets through the filter you spent a day building.

The thirty-minute project becomes a week-long project. The week-long project becomes something you maintain indefinitely.

The argument for not building your own form backend isn't that it's impossible. It's that the scope is almost always larger than it appears from the start, it lands in a category where purpose-built services are good and mature, and your time is worth more than the service costs. The math almost never favors rolling your own.

Here's the full picture of what you're actually building.

What you're actually building

A production-ready form endpoint isn't one piece of code. It's a stack of interacting problems, each requiring its own implementation, all of which have to work together in production.

CORS configuration. Your form lives on one domain; your API lives on another. The browser's security model requires explicit permission for cross-origin requests, expressed as specific HTTP response headers on every response. Get them wrong and every submission is silently blocked in production while working fine on localhost. You also have to handle the preflight OPTIONS request the browser sends before each POST, or the same thing happens.

Server-side validation. Client-side validation improves UX but doesn't protect your endpoint. Anyone can disable JavaScript, modify the DOM, or send raw HTTP requests directly to your server. You need to re-validate every constraint on the server: required fields, email format, message length, acceptable character ranges. Every field, every time, regardless of what the frontend already checked.

Spam filtering. Public form endpoints get found by bots quickly. An effective filter needs multiple layers working together: honeypot fields that real users never fill but bots typically do; rate limiting that caps submissions per source per time window; content scoring that assigns a probability score rather than a binary pass/fail. Each layer requires its own implementation, and the whole thing needs ongoing tuning as spam patterns evolve.

Data storage. Relying on email delivery as your only record of a submission is fragile. Email providers have outages. Notifications land in spam folders. Recipient addresses change. Without a database record created before the email goes out, you lose the submission with no recovery path. You need a storage layer: schema design, query logic, and a system to maintain over time.

Reliable email delivery. Sending email reliably requires more than calling a send function. You need a transactional email provider, correctly configured SPF and DKIM DNS records on your sending domain, error handling for bounces and rejections, and a retry policy for transient failures. You also need visibility into delivery so you know when it breaks, because silent failures are the norm without explicit monitoring.

Rate limiting. Related to spam filtering but distinct: a rate limiter tracks submission volume by IP or by time window and blocks sources that exceed a threshold. It requires persistent state, Redis or a database table, because in-memory counting breaks as soon as you have more than one server instance or more than one serverless function invocation.

Response format handling. A plain HTML form submission expects a redirect to a confirmation page. A fetch-based form expects a JSON response. Handle the wrong one and the frontend reports an error even when the submission succeeded on the backend. You need to detect which kind of request you're receiving and respond accordingly.

Error visibility. When something breaks, who finds out? A silent failure in email delivery goes unnoticed until a user complains. You need structured logging, ideally alerting, and a way to audit what's been received versus what's actually been delivered.

None of these is a hard problem in isolation. The issue is that you need all of them working together, they interact with each other in ways that surface at runtime rather than in development, and maintaining them across changing dependencies and evolving spam patterns is an ongoing commitment.

For a more detailed walkthrough of each layer and why it exists, the hidden complexity of handling form submissions maps out the full stack.

The time arithmetic

The case for building your own usually rests on the assumption that it's cheaper because your time is free. That's the wrong accounting.

Your time has a cost, whether or not it shows up on an invoice. Two days spent on form infrastructure is two days that could have gone toward whatever your project is actually about. A hosted form service costs a few dollars a month. The breakeven on developer time versus subscription cost happens almost immediately, on the first afternoon you would have spent debugging CORS configuration.

There's also a maintenance dimension that's easy to underestimate upfront. A form backend you build is a form backend you maintain. Spam patterns evolve and you'll need to update your filters. Email providers update their APIs and you'll need to update your integration. When a new bot wave gets through in six months, you're the one debugging it on a Tuesday evening instead of building the next feature.

That ongoing cost doesn't disappear from your time budget. It arrives later, in less predictable increments, when you'd rather be doing something else. Common mistakes developers make with contact forms covers several of the places this surfaces in practice.

When building your own actually makes sense

There are genuine reasons to build your own form backend.

Regulatory requirements. If your data must remain within a specific jurisdiction or within infrastructure you fully control, a hosted service may not satisfy your compliance requirements. Build your own in that case, with clear requirements and the right infrastructure from the start.

Deep system integration. If form submissions need to trigger complex business logic that can't be handled via webhook, integrate with proprietary internal systems, or flow through a specific data pipeline that can't be reached from an external service, a general-purpose endpoint won't be the right fit. You'd end up building a wrapper that's more complex than handling submissions directly.

The form backend is your product. If you're building a form service, you need to build the infrastructure. That's the business.

Outside these cases, the arguments for building your own tend not to hold up under examination. "I want full control" sounds reasonable until you consider what fully owning this stack requires. "I don't want another dependency" underestimates how quickly the custom implementation becomes its own maintenance burden. A hosted service is a dependency you can replace; a custom backend you've accumulated over two years is harder to get out from under.

What a hosted endpoint actually gives you

When you point a form at a hosted endpoint, you're not making a compromise. You're skipping the implementation list above and getting a result that is, in most cases, more complete and more reliable than what you'd build yourself in the same timeframe.

The HTML integration is a single attribute change:

<form action="https://formtorch.com/f/YOUR_FORM_ID" method="POST">
  <input name="name" type="text" required />
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

CORS is handled at the endpoint. Every submission is stored before email delivery is attempted, so a delivery failure doesn't lose the submission. Spam filtering and rate limiting run on every request. You get a submission log in the dashboard with no additional setup. The full feature set includes autoresponders, webhook delivery, and CSV export on top of the basics.

For fetch-based JavaScript forms in React, Next.js, or any other framework, the integration is equally direct. The complete guide to contact forms for developers covers both approaches: plain HTML forms and JavaScript-driven submissions with loading state and error handling.

The point isn't that using a service is the lazy option. It's that form handling is a solved category of problem, with mature services built specifically to handle it reliably. Using one is the correct engineering decision for most projects.

The actual question to ask

The useful question isn't "should I build my own form backend?" It's "is there a specific reason I need to own this infrastructure?"

For a portfolio site, a marketing page, a client project, a SaaS landing page, or any other project where the form backend isn't the differentiator: use a service. The implementation takes minutes, the maintenance burden is zero on your end, and the security and reliability characteristics are better than most custom implementations.

If you have a genuine reason to own the infrastructure, knowing that reason upfront changes how you build it. You'll invest in monitoring, in spam filter tuning, in email delivery reliability, because you know you need to. Building without that clarity is how a quick API route turns into two weeks of unplanned infrastructure work.

Don't build it unless you have a real reason to. For everything else, there's a service that already exists.

Skip the infrastructure.

Formtorch handles storage, spam filtering, email delivery, and rate limiting. Create a free account and get your endpoint URL in two minutes.

Related posts
Rate limiting strategies for form endpoints
Rate limiting strategies for form endpoints
EngineeringJune 30, 202612 min read
How to design a scalable form validation system
How to design a scalable form validation system
EngineeringMarch 12, 20266 min read