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.
1. Vision & Problem
Section titled “1. Vision & Problem”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).
2. Core Principle (this has to stick)
Section titled “2. Core Principle (this has to stick)”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.
3. Architecture (Overview)
Section titled “3. Architecture (Overview)” 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:
- API service — long-running (Go/Deno), accepts challenge and submit requests, runs the validation pipeline, sends mail.
- 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.
- Data store — tenants, allowed origins, quotas, signing secrets, consumed nonces, submission log/audit, blocklists.
- SMTP delivery — via the gateway’s own mail server, fixed DKIM sender domain.
- (optional) Queue/worker — asynchronous delivery, retries, peak buffering.
4. The Core: Challenge/Handshake Flow
Section titled “4. The Core: Challenge/Handshake Flow”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.
- Page loads → JS calls
GET /v1/challenge?site=SITEKEY.- The gateway checks the
Originagainst the tenant’s allowlist. - Returns an HMAC-signed challenge token (server secret), containing:
tenant,origin,issued_at, a one-time nonce, optionalpow_difficulty, a short TTL (e.g. 5–10 minutes).
- The gateway checks the
- Client optionally solves the proof-of-work and/or obtains a captcha token.
POST /v1/submitwith: challenge token, PoW solution, captcha token, honeypot field, form data.- 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.
5. Security Layers (in detail)
Section titled “5. Security Layers (in detail)”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.
5.3 Proof-of-Work (PoW)
Section titled “5.3 Proof-of-Work (PoW)”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.
5.4 Captcha (optional, per tenant)
Section titled “5.4 Captcha (optional, per tenant)”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.
5.5 Multi-dimensional rate limiting
Section titled “5.5 Multi-dimensional rate limiting”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.
5.6 Content/spam scoring
Section titled “5.6 Content/spam scoring”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.
5.7 Honeypot + timing traps
Section titled “5.7 Honeypot + timing traps”An invisible field (bot fills it in → discard). Cheap, catches the dumb mass.
5.8 Tenant isolation & secrets
Section titled “5.8 Tenant isolation & secrets”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.
5.11 Input hardening
Section titled “5.11 Input hardening”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.
5.12 Abuse response playbook
Section titled “5.12 Abuse response playbook”Defined steps in case of abuse: throttle tenant → quarantine → disable; block IP/ASN; rotate signing secret; notify the operator.
6. The Small Admin Backend
Section titled “6. The Small Admin Backend”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.
7. Data Model (sketch)
Section titled “7. Data Model (sketch)”- 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.
8. API Design (sketch)
Section titled “8. API Design (sketch)”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.
9. Tech Stack Considerations
Section titled “9. Tech Stack Considerations”- 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): readsdata-attributes on the<form>(site key, target), performs challenge→PoW→submit, shows inline success/error. - Progressive enhancement: an optional
mailtofallback 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.
13. Roadmap: MVP → Later
Section titled “13. Roadmap: MVP → Later”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.
14. Open Questions
Section titled “14. Open Questions”- 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.