Skip to content
Back to the blog

Static forms: check the backend and email delivery

Separate browser validation, backend processing and actual delivery. Local synthetic diagnostics, abuse-control limits and a Node example without sending email.

  • Forms
  • Node.js
  • AI

A button changing to “thank you” does not prove that anyone received a message. An AI-built website can have a polished form and browser validation without a backend to process requests. Before buying or changing hosting, identify which stage works and which is missing.

This guide uses Buzón de ensayo, an original MIT Node example. The public repository contains code for reproducing local tests, not a public backend demo. Normal mode validates synthetic data without sending email or storing contacts.

Four stages, four kinds of evidence

Stage What you can check What it does not prove
Browser Required fields, format and a Network request. An existing backend with the same rules.
Server Endpoint response and validation. A provider call took place.
Provider Documented API acceptance and identifier. Arrival in the destination inbox.
Actual delivery Delivery outcome, bounces and an authorised test destination. Guaranteed future delivery.

MDN form validation explains why browser checks also need server-side validation. A client can bypass the UI. Inspect URL, method, status and response in DevTools: 200 means only what that endpoint defines.

A local test without a provider

With a copy of the example, install Node 22.23.3 from Node.js and check for v22.23.3. Retain the lockfile and licence. There are no external dependencies or credentials.

git clone https://github.com/ocentelles/hostbrid-example-form.git
cd hostbrid-example-form
git checkout e949f6a14258eced69d1a3e47dbb6909b35639cc
node --version
npm ci --ignore-scripts --offline
npm run check
npm test
PORT=8080 npm start

Open http://127.0.0.1:8080/ with synthetic data such as demo@example.invalid. Keep the process running. It binds only to loopback; this is not a public HTTPS demo. Use that exact origin: localhost is not equivalent. FORM_ORIGIN sets the allowed origin, not DNS, certificates or public access.

From another terminal, target only this local demo:

curl -fsS http://127.0.0.1:8080/healthz
curl -s -i http://127.0.0.1:8080/api/contact \
  -H 'Origin: http://127.0.0.1:8080' \
  -H 'Content-Type: application/json' \
  --data '{"name":"Persona Demo","email":"demo@example.invalid","message":"Mensaje sintético de ensayo.","website":""}'

Expect 200, accepted:true and delivery:"not-sent". The receipt confirms local validation, not email transport. /healthz shows delivery disabled and an ephemeral counter; it does not check a provider or mailbox.

Test rejection, not just the home page

curl -s -i http://127.0.0.1:8080/api/contact \
  -H 'Origin: https://other.example.invalid' \
  -H 'Content-Type: application/json' \
  --data '{"name":"Persona Demo","email":"demo@example.invalid","message":"Mensaje sintético de ensayo.","website":""}'

Expect 403. Tests also cover missing/null origin, incorrect type 415, invalid JSON/UTF8 400, bodies above 4096 bytes 413, incomplete reads 408 and invalid fields 422. Undeclared routes return 404 and unsupported methods 405. After five POST attempts per minute per socket address, further attempts return 429; invalid attempts count too.

These tests are local and synthetic. Do not run the load experiment against somebody else’s form or mistake a basic limit for a DDoS guarantee. The example ignores X-Forwarded-For; behind a proxy, visitors may share one address. Public use requires a trusted proxy configuration and shared limits.

What the controls can and cannot establish

Origin and Fetch Metadata reduce cross-origin browser requests, but do not authenticate bots forging headers. The honeypot requires an empty field; it is a heuristic, not a CAPTCHA. Email syntax does not prove ownership or delivery. The rate limiter is per process and resets with it.

The second phase injects local mocks for verification and delivery in explicit test mode only. It checks denial, errors and timeouts without verifying a person or connecting a provider. An idempotency key binds content to an attempt: concurrent retries share execution; changed content or a reused token with another key returns 409. Key and hash caches and replay protection last five minutes in memory, without distributed or durable guarantees. A timeout cannot establish that an external provider cancelled an effect.

Turnstile requires server-side Siteverify; displaying a widget does not complete the integration. This example has no widget or real token verification. A future integration needs a server-only secret and expected hostname and action, failure and replay checks.

Before claiming actual delivery

The example passed 43 local tests with browser checks in Chrome at 390 and 1280 px. This verifies its local contract and mocks, not SMTP, Nodemailer, OAuth, automatic push deployment, a public domain or email receipt. No provider was selected or account purchased for this example.

A sending provider needs its own configuration: operator-controlled sender/recipient, server-only key, domain verification where applicable, error/retry handling and an authorised test destination. Clients should not choose arbitrary recipients or senders. Resend documents sending states; it is a provider reference, not an integration tested here. Review events, bounces and the destination: acceptance is not inbox arrival.

Choose where the backend runs

For an application needing a permanent Node process, consider Projects with a plan that includes services and available quota. A VPS may fit when you need environment control and take responsibility for the system, network and operation. The current example binds only to loopback; hosting adaptation and HTTPS need separate tests. Static quota does not imply free services.

If you are still identifying the architecture, read hosting for an AI-built website and Node process diagnostics. Domain email covers mailboxes; buying a mailbox does not establish a transactional form backend. Decide sending and hosting separately, checking current terms and pricing.

Manage your sites, servers and email.

Forgot your password?

Free, and it takes a minute.

At least 8 characters. Not all digits, not a common one.

We'll email you to confirm the account.