FormHub
A form backend for static sites that does not lose enquiries
Takes form submissions from static sites, commits them to Postgres before acknowledging, then delivers the email alert from a retrying queue — so a failed notification is visible and replayable rather than silent.
Language
TypeScript
Licence
Not yet declared
Status
Beta
Repository
webloomlabs/Form-hub
Hosting
Vercel + Neon, or Docker Compose on a VPS
Tenancy
Agencies, their clients, and per-site logins
Why it exists
The n8n workflow
fails quietly. This does not.
The webhook-workflow approach to contact forms loses enquiries silently. A node errors, a credential expires, the instance is down — and nobody finds out until a client asks why they never heard back about a job they lost.
FormHub makes a narrower promise and keeps it: if the endpoint returned success, the submission is already on disk. The submission and its delivery job are written in one transaction, so a submission can never exist without something queued to tell a human about it.
Everything after that is recovery rather than hope. Every delivery attempt writes a row, failures retry on a fixed schedule, dead jobs can be resent from the console, and jobs orphaned by a killed container return to the queue on their own.
What it does
What FormHub gives you
The short version of the README. The repository has the rest, including what FormHub does not do yet.
Durable before acknowledged
The submission and its delivery job commit in a single transaction. A 200 response means the enquiry is stored, not that it was handed to something else.
Retries you can watch
Immediate, then 30s, 2m, 10m, 1h, 6h, then dead-lettered. Every attempt is recorded, and the delivery log renders the whole history.
Safe concurrent workers
Jobs are claimed with FOR UPDATE SKIP LOCKED, so running more workers is safe by construction rather than by convention.
Native HTML or JSON
A plain form POST redirects to your thank-you page with no JavaScript; a JSON request stays on the page. Origins are registered per site.
Spam quarantined, not refused
Honeypot and timing hits are stored as spam and answered normally, so a false positive never costs a real enquiry and a bot never learns it was caught. Rate limits and Turnstile handle the rest.
Built for agencies
One console manages clients, sites and forms; each website owner gets their own login and their own inbox, and agencies can send from their own SMTP.
Getting started
Running locally
Postgres, Mailpit and MinIO come up in Docker; the app runs on your machine against them.
Set up
docker compose up -d postgres mailpit minio
cp .env.example .env.local
npm install
npm run db:migrate
npm run db:seed
npm run devThe app is on http://localhost:3100 and Mailpit catches outgoing mail on http://localhost:8025. Generate AUTH_SECRET, ENCRYPTION_KEY and CRON_SECRET before the first run.
Submitting a form
<form action="https://formhub.example/f/YOUR_KEY" method="POST">
<input name="name" required>
<input name="email" type="email" required>
<textarea name="message" required></textarea>
<button>Send</button>
</form>The console renders this snippet with each form's real key, including the honeypot field.
Built with