Rent a form service, or own the backend.
Most form tools are hosted-only: you pay per submission, your data sits on their servers, and the embed is a black box. This is the other option.
Static site, Jamstack, or plain HTML — it just mounts.
No bundler, no npm install. Configure everything through data-attributes; the widget reads them and renders itself.
<div data-easycontact="f3a9c1b7e2d4"
data-easycontact-theme="dark"
data-easycontact-layout="page"
data-easycontact-heading="Get in touch"></div>import { ContactForm } from './ContactForm';
<ContactForm token="f3a9c1b7e2d4" theme="auto" />A backend whose whole job is to never drop a message.
Stored the instant it's submitted, addressable only by an unguessable ID, and surfaced in a dashboard that flags it amber until it's answered. The hard parts are done; you own the result.
The security-sensitive parts, handled.
The details that are easy to get wrong when you roll your own — and expensive when you do.
Unguessable identifiers
Every external ID is a random UUIDv4 or hex token. Sequential auto-increment keys never leave the server, so nobody can walk to a neighbouring record.
Uniform 404 authorization
Missing row or not yours? Identical 404. A 403 would confirm the ID exists — exactly what an attacker is fishing for. Scoping is enforced server-side.
Honeypot spam protection
A hidden field silently drops bots at submit time. No CAPTCHA, no third-party risk-scoring, no friction for real visitors.
Idempotent migrations
The migration runner records what it applied and is safe to run twice. It converts a pre-forms database in place without changing existing tokens.
Graceful fallback fields
If a form’s config can’t be fetched, the widget renders classic name/email/message fields the backend still accepts — never a blank page.
Multi-form data model
Projects own forms; forms own ordered field definitions; submissions snapshot field labels and types, so editing a form never rewrites history.