Skip to content

Concept

Standalone concept — deliberately written so that it could be pulled out of this repo and set up as a separate project. This is not about the contact form on this one site (that comes later as a small intermediate step), but about a reusable service for many static websites.


Static sites (Astro, Hugo, Eleventy …) are fast, secure, and cheap — but they cannot send emails: no backend, no secret that could be safe in the browser. Every static site eventually runs into the same wall: contact, lead, application, or order forms.

There are third-party services (Formspree, Web3Forms, Basin, Getform, Formsubmit), but:

  • Data passes through a third party, usually US-based → GDPR friction.
  • Recurring costs per site, vendor lock-in.
  • Little control over delivery, reputation, retention.

Goal: A self-hosted, GDPR-clean gateway that serves many sites at once. Build it once → use it for all future Astro projects. A perfect fit for an agency with its own infrastructure and its own mail server — and potentially marketable in its own right (see §12).


On static sites, there is no secret in the browser. Everything in the client-side JS is public. Consequences:

  • The “API token” is not authentication, but a public identifier (site key): it only tells the gateway “which tenant am I” → which allowed domains, which quota, which protection config apply.
  • Security here doesn’t mean “keep unauthorized parties out,” it means making abuse expensive, worthless, visible, and shutdown-able — and protecting the shared sending reputation.
  • Defense in depth: no single silver bullet, but layers that together raise the bar.

Static site (browser) Techeve Form Gateway
┌───────────────────────┐ ┌──────────────────────────────────────┐
│ Form + snippet │ │ API service (long-running) │
│ (site key, public) │──1────▶│ GET /v1/challenge │
│ │◀───────│ POST /v1/submit │
└───────────────────────┘ │ │ │
│ ▼ │
│ Validation pipeline (see §5) │
│ │ ok │
│ ▼ │
│ Delivery via own SMTP (DKIM) │
└───────┬───────────────┬──────────────┘
│ │
┌─────▼─────┐ ┌─────▼──────┐
│ Data store│ │ Admin UI │
│ (tenants, │ │ (domains, │
│ logs, │ │ quotas, │
│ nonces) │ │ kill sw.) │
└───────────┘ └────────────┘

Building blocks:

  1. API service — long-running (Go/Deno), accepts challenge and submit requests, runs the validation pipeline, sends mail.
  2. Admin backend + UI — this is where tenants/domains/quotas are managed (the “small backend,” §6). Unlike the site frontends, this is a real backend with real authentication.
  3. Data store — tenants, allowed origins, quotas, signing secrets, consumed nonces, submission log/audit, blocklists.
  4. SMTP delivery — via the gateway’s own mail server, fixed DKIM sender domain.
  5. (optional) Queue/worker — asynchronous delivery, retries, peak buffering.

Instead of “one POST → email goes out,” a two-stage flow. This is the single most important measure, because a naive bot that only POSTs has no valid ticket at all — and cannot forge one.

  1. Page loads → JS calls GET /v1/challenge?site=SITEKEY.
    • The gateway checks the Origin against the tenant’s allowlist.
    • Returns an HMAC-signed challenge token (server secret), containing: tenant, origin, issued_at, a one-time nonce, optional pow_difficulty, a short TTL (e.g. 5–10 minutes).
  2. Client optionally solves the proof-of-work and/or obtains a captcha token.
  3. POST /v1/submit with: challenge token, PoW solution, captcha token, honeypot field, form data.
  4. Gateway verifies (order = cheapest first, see §5) and sends or rejects.

The handshake simultaneously provides replay protection (nonce used once), a timing check (too fast = bot), and origin binding.


Order in the pipeline: cheap/hard checks first, expensive ones last. Each layer states what it does — and what it doesn’t.

5.1 Origin/Referer allowlist (server-side!)

Section titled “5.1 Origin/Referer allowlist (server-side!)”

The gateway checks the Origin header against the domains registered for the site key. Important: CORS is enforced by the browser, not the server — a bot using curl ignores CORS. So always check and reject server-side. Also: Origin/Referer can be forged by non-browser clients. → Protects against “form copied on a foreign site” and browser-based misuse. Does not protect against headless bots. A useful but weak layer.

5.2 Signed challenge + nonce + TTL + timing

Section titled “5.2 Signed challenge + nonce + TTL + timing”

See §4. HMAC with a server secret → cannot be forged. Nonce → replay protection. TTL → no hoarding. Minimum fill time → too fast = bot. The backbone of the system.

The challenge requires solving a computational puzzle (Hashcash-style) that the browser must solve. Every submission costs CPU time → mass sending becomes expensive. Self-hostable, no third party, GDPR-clean. Difficulty can be tuned per tenant/adaptively.

When things get tough: Turnstile / hCaptcha / Friendly Captcha (EU). Token verified server-side. The strongest anti-bot layer, but more friction and possibly a third-party request. A toggleable option per tenant.

Not just per IP (IPs rotate cheaply via botnets/proxies). Additionally:

  • per site key (tenant quota, e.g. 100 emails/day),
  • per recipient/form,
  • globally (emergency stop / circuit breaker),
  • adaptive (exponential backoff on suspicion).
  • ASN/IP reputation: datacenter/Tor ASNs are suspicious for a contact form; known bad ranges get blocked.

A score instead of a hard block: number of links, keywords, language mismatch, all-caps, Bayes/Rspamd. Above a threshold → quarantine (not sent, but visible in the admin UI) instead of silently discarding.

An invisible field (bot fills it in → discard). Cheap, catches the dumb mass.

Per tenant: a public site key (in the JS) and a private signing secret (only in the gateway). SMTP credentials live only in the gateway. Compromising a site frontend leaks nothing sensitive.

5.9 Monitoring, anomaly detection, kill switch

Section titled “5.9 Monitoring, anomaly detection, kill switch”

Abuse will happen eventually. What matters: seeing spikes per tenant, being alerted, and being able to disable a tenant immediately. Responsiveness beats perfect prevention.

5.10 Deliverability & reputation protection (critical in multi-tenancy!)

Section titled “5.10 Deliverability & reputation protection (critical in multi-tenancy!)”

The gateway always sends from its own DKIM/SPF/DMARC-signed sender domain; Reply-To = sender; no freely chosen From. Otherwise one abused tenant sending spam gets the shared sending IP onto blocklists — and all customer sites can no longer send anything. That’s why tenant quotas + monitoring are reputation-critical here, not “nice to have.” No backscatter: no automatic bounces/confirmation emails to foreign addresses without a strict rate brake.

Field whitelist, length limits, header-injection protection (no \r\n in subject/sender), attachment policy (types/size/count, or no attachments at all in the MVP), encoding checks.

Defined steps in case of abuse: throttle tenant → quarantine → disable; block IP/ASN; rotate signing secret; notify the operator.


The only part with real authentication (admins have real secrets). Responsibilities:

  • Create/manage tenants: generate site key, maintain domain/origin allowlist, set quotas, recipient/routing rules, captcha on/off, PoW difficulty, active/inactive.
  • Dashboard: submissions, spam/quarantine rate, quota utilization, error rate — per tenant.
  • Quarantine view: inspect blocked/suspicious emails, release or discard.
  • Kill switch per tenant, blocklists (IP/ASN/email pattern).
  • Audit log.

MVP: a simple admin UI suffices (or CLI/DB to start with), a polished interface later.


  • tenants: id, name, site_key (public), signing_secret, smtp_route/recipient, daily_quota, pow_difficulty, captcha_cfg, active, created_at.
  • origins: tenant_id, origin (allowed domain) — n:1.
  • nonces: nonce, tenant_id, expires_at, used_at (replay protection; TTL cleanup).
  • submissions: id, tenant_id, received_at, ip_hash, origin, score, status (sent/quarantined/blocked), payload_snippet (per policy), error. (Retention limited, see §10.)
  • rate_buckets: counters per IP/site key/window (or in Redis).
  • blocklist: IP/ASN/email pattern.

  • GET /v1/challenge?site=SITEKEY{ challenge: "<hmac-token>", pow: { difficulty }, captcha?: { provider, sitekey } }
  • POST /v1/submit → Body: { site, challenge, pow_solution, captcha_token, hp (honeypot), fields: {…} }200 { ok: true } | 4xx { ok: false, reason }
  • Admin (auth): POST /admin/tenants, GET /admin/tenants/:id/stats, POST /admin/tenants/:id/disable, GET /admin/quarantine

Responses are deliberately low on information externally (no hints as to which layer triggered), details only in the admin log.


  • API service: Go (a single binary, few dependencies, robust, ideal as a long-running service) or Deno (close to the JS stack, TS, secure by default). Both run cleanly as a systemd service / container on Proxmox.
  • Data store: PostgreSQL (multi-tenancy, reporting) or SQLite to start. Redis optional for rate limits/nonces (fast, native TTL).
  • Delivery: own mail server (SMTP 587, STARTTLS), DKIM/SPF/DMARC maintained.
  • Deployment: own server / Proxmox VM / container. nginx reverse proxy.

10. GDPR / Data Protection (a selling point!)

Section titled “10. GDPR / Data Protection (a selling point!)”
  • EU-hosted, own infrastructure → clean DPAs (data processing agreements) possible.
  • Data minimization: only necessary fields, IP only hashed/short, submission log with short retention (e.g. 30 days) + auto-deletion.
  • Self-hosted PoW/captcha possible → no Google/US third party.
  • Transparent documentation of what is stored — exactly your “data minimization” claim.

11. Client Integration (how an Astro site uses it)

Section titled “11. Client Integration (how an Astro site uses it)”
  • A tiny JS snippet or npm package (@techeve/form-gateway): reads data- attributes on the <form> (site key, target), performs challenge→PoW→submit, shows inline success/error.
  • Progressive enhancement: an optional mailto fallback without JS.
  • Result for the developer: form + one snippet + enter site key → done. No more backend per project.

12. Marketing as a Service (initial thoughts)

Section titled “12. Marketing as a Service (initial thoughts)”

Positioning:EU-/GDPR-first form backend for static sites — self-hosted or managed.” This is the gap that Formspree & co. leave open for the DACH market (Germany, Austria, Switzerland).

Target audience: Agencies and Jamstack/Astro developers, especially in the GDPR-sensitive DACH region; own customer projects as the first market (dogfooding).

Differentiation vs. Formspree/Web3Forms/Basin:

  • EU-hosted, DPA/GDPR out of the box.
  • Self-hosted option (no vendor lock-in, data sovereignty).
  • No third-party captcha needed (own PoW).
  • Transparent, fair quotas.

Business models (options):

  • Managed SaaS: free tier (e.g. 50 submissions/month), then tiered pricing by volume/forms/tenants.
  • Self-hosted license: one-time/annual, for companies with a data sovereignty requirement.
  • Hybrid: managed + “bring your own SMTP.”
  • Bundle as an add-on to your web/hosting packages.

Branding: “Techeve Form Gateway” — fits your own products (DNS editor, mail server). Positions Techeve as a provider of its own, data-sovereign building blocks.

Go-to-market: 1) use internally for your own projects first (hardens and references the product), 2) open as a beta to friendly agencies, 3) public free tier + landing page.


MVP (internal, one/few tenants):

  • Challenge flow (HMAC + nonce + TTL + timing), honeypot, rate limiting, PoW, origin check, SMTP delivery with DKIM, minimal admin (DB/CLI).

v2 (multi-tenant product):

  • Admin UI, quotas, captcha option, spam scoring/quarantine, dashboards, kill switch, npm client package.

v3 (SaaS):

  • Self-service registration, billing, usage metrics, status page, self-hosted distribution.

  • Allow attachments? (Risk/effort — probably no in the MVP.)
  • Incoming confirmation emails to the sender (double opt-in) — yes/no, given backscatter risk only with strict rate limiting.
  • One shared SMTP/sender for all tenants, or “bring your own”? (Reputation trade-off.)
  • Work out free-tier limits and abuse economics.
  • Hosting region/redundancy for managed operation.