Open Source ·

Edge Form

Serverless form inbox for static sites — submit endpoint, D1 archive, abuse controls, email/webhook delivery, and admin export.

  • Cloudflare Workers
  • D1
  • TypeScript
  • Serverless
  • Open Source

edge-form-inbox gives static websites a production-ready contact form backend without running a server. Submissions are validated at the edge, stored in D1, protected from basic abuse, optionally forwarded to email or webhooks, and exportable from protected admin routes.

At a glance

  • Role: Solo product engineer across architecture, Worker API, D1 schema, abuse controls, delivery adapters, and deployment documentation.
  • Scope: Public form endpoint, CORS, validation, spam checks, D1 inbox, delivery event logging, admin list/detail routes, and CSV export.
  • Stack: Cloudflare Workers, D1, TypeScript, Wrangler, Resend API, signed webhooks.
  • Status: Functional MVP ready for static sites, portfolios, landing pages, and client projects.
  • Proof: Serverless submit flow, durable submission archive, hashed IP rate limiting, adapter audit trail, and bearer-token admin access.

The problem

Static sites are fast, cheap, and easy to deploy, but contact forms still need a backend. Teams usually end up choosing between third-party form services, email-only submissions, or a custom server that is too much infrastructure for a simple site.

That creates a few practical problems:

  • A static site cannot receive form submissions by itself.
  • Hosted form services add lock-in, recurring cost, and external data retention.
  • Email-only delivery loses the source of truth when a message bounces or gets filtered.
  • Public forms need basic abuse controls, even on small projects.
  • Client sites need a simple operational handoff, not a full application server.

The goal was to build a small, self-hostable form inbox that can sit behind any static website, store every accepted submission, and forward notifications without requiring traditional backend infrastructure.

What I built

edge-form-inbox is a Cloudflare-native form backend with one public submission API and a protected admin/export API.

Core features:

  • Public submission APIPOST /api/submit accepts JSON, application/x-www-form-urlencoded, and multipart form submissions.
  • Payload normalization — common fields like name, email, subject, and message are normalized while extra safe fields are preserved as metadata.
  • Spam and abuse controls — honeypot detection, message length validation, excessive-link checks, body-size limits, and D1-backed rate limiting.
  • Privacy-aware IP handling — raw IP addresses are not stored. IPs are hashed with a deployment secret before being used for rate limits.
  • D1 submission inbox — accepted, flagged, and rejected submissions are persisted with timestamps, metadata, user agent, spam status, delivery status, and delivery errors.
  • Delivery adapters — Resend email notifications and signed webhook delivery can be enabled independently.
  • Admin and export API — bearer-token protected routes list submissions, fetch a single submission with delivery events, and export CSV.

How it works

Static site / HTML form

        │ POST /api/submit

Cloudflare Worker
parse · validate · CORS · honeypot · rate limit · hash IP

        ├── D1: submissions, rate_limits, delivery_events

        └── waitUntil(): Resend email and signed webhook delivery

Admin API
list · detail + events · CSV export

The system is intentionally small: one Worker, one D1 database, environment variables for configuration, and optional delivery integrations. There are no queues, containers, or long-running servers to operate.

Request pipeline

  1. Parse the body as JSON, URL-encoded, or multipart form data.
  2. Enforce size and content-type limits before trusting the payload.
  3. Hash the client IP using IP_HASH_SECRET so raw addresses are not persisted.
  4. Apply rate limiting through the D1-backed rate_limits table.
  5. Check spam signals: honeypot field and excessive links.
  6. Normalize and validate the submission.
  7. Persist the record to D1.
  8. Dispatch delivery adapters with ctx.waitUntil() so the client gets a fast response while email/webhook work continues.
  9. Record adapter results and update the final delivery status.

Key decisions

Store the truth before delivery

Email and webhook delivery are best-effort. D1 persistence happens first, so a submission is not lost if Resend is down, a webhook fails, or an adapter is missing configuration.

Keep privacy defaults practical

The Worker never stores raw IP addresses. Rate limiting still works, but the stored value is a salted hash. That is enough for basic abuse protection without keeping direct identifiers in the inbox.

Accept common static-site payloads

The endpoint supports the formats developers actually use for simple forms: JSON, URL-encoded bodies, and multipart form data. Custom fields are preserved as metadata so each site can add fields without a schema migration.

Make adapter failure visible, not fatal

Delivery adapters return sent, failed, or skipped. Each attempt is recorded in delivery_events, and the submission receives a final delivery status. The public form flow does not crash just because an optional notification channel is misconfigured.

API surface

RoutePurpose
GET /healthHealth check for deployments and uptime checks
POST /api/submitPublic submission endpoint for browser forms
GET /api/admin/submissionsBearer-token protected list/search endpoint
GET /api/admin/submissions/:idSubmission detail plus delivery event history
GET /api/admin/export.csvCSV export for spreadsheet or client handoff

Configuration

AreaConfig
Rate limitRATE_LIMIT_MAX, RATE_LIMIT_WINDOW_SECONDS
Spam checksHONEYPOT_FIELD, MAX_LINKS
ValidationMESSAGE_MIN_LENGTH, MESSAGE_MAX_LENGTH, MAX_BODY_BYTES
CORSALLOWED_ORIGINS
Admin authADMIN_TOKEN
PrivacyIP_HASH_SECRET
EmailRESEND_API_KEY, MAIL_FROM, MAIL_TO
WebhookWEBHOOK_URL, WEBHOOK_SECRET

Outcome

The MVP proves a complete serverless form workflow for static websites: submissions can be accepted safely, stored durably, forwarded through optional adapters, inspected through protected admin routes, and exported for handoff.

It is intentionally small enough to self-host, adapt for client projects, and use as a reference architecture for edge-native form handling.

What I would add next

  • Optional Cloudflare Turnstile verification for stronger bot protection.
  • A small admin dashboard UI on top of the existing admin API.
  • Retry controls for failed delivery events.
  • Per-form configuration for multi-site deployments.
  • Retention policies for old submissions and rate-limit rows.
  • Tests around request parsing, validation, CORS, rate limiting, and adapter status transitions.

Have a similar system to build?

Send the product goal, timeline, and current blockers. I’ll help you turn it into a practical build plan.

Start a conversation

All works