DEV Community

Cover image for I built a form abandonment recovery SaaS entirely on Cloudflare's developer platform
jasonm4130
jasonm4130

Posted on

I built a form abandonment recovery SaaS entirely on Cloudflare's developer platform

81% of people who start filling out a web form abandon it before submitting (The Manifest, 2018). For ecommerce, cart abandonment recovery emails convert about 10% of those back into customers. But for lead gen forms, contact forms, quote request forms — there's been no equivalent.

I built FormRecap over an Easter long weekend to fix this. It's a 2.7KB JavaScript snippet that detects form abandonment and sends recovery emails with magic links that restore the visitor's exact form state.

The entire product — API, database, session tracking, email dispatch, billing, and dashboard — runs on Cloudflare's developer platform. Here's how.

The architecture

Snippet (2.7KB IIFE)
  ↓ sendBeacon / fetch
Cloudflare Worker (Hono API)
  ↓
Durable Object (per-form session tracking)
  ↓ alarm fires after abandonment delay
Queue → Workflow → Resend (recovery email)
  ↓
Visitor clicks magic link → form restored
Enter fullscreen mode Exit fullscreen mode

The stack:

Service Purpose
Workers API + SPA serving
D1 All relational data
Durable Objects Per-form session state + abandonment timing
Queues + Workflows Async recovery email pipeline
KV Config cache, session tokens, rate limiting
Analytics Engine High-volume event metrics

The snippet

The client-side snippet is the most constrained part of the system. It runs on customer websites, so it has to be:

  • Tiny — 2.7KB gzipped, zero dependencies
  • Compatible — ES2015 target (no optional chaining, no nullish coalescing, no async/await)
  • Privacy-first — excludes passwords, credit cards, SSNs, and 31 other sensitive field patterns by default, plus value-level regex for credit card and SSN formats
  • Non-blocking — uses sendBeacon with text/plain content type to stay CORS-safelisted (no preflight requests)

It discovers forms via querySelectorAll with a body-level MutationObserver for SPAs, tracks field events (focus, blur, change, input), and detects abandonment through visibilitychange, pagehide, beforeunload, and SPA navigation (pushState/replaceState/popstate).

Per-customer encryption

Since the snippet captures form field data, security couldn't be an afterthought. Every customer's data is encrypted with its own key:

customerKey = HKDF(masterSecret, salt=siteId, info="formrecap-field-enc")
Enter fullscreen mode Exit fullscreen mode

Field data is encrypted with AES-GCM-256 using that derived key. Email addresses use HMAC blind indexes for searchable lookups without storing plaintext. All signature and hash comparisons use timing-safe comparison to prevent timing attacks.

What surprised me

Durable Objects for session tracking turned out to be the perfect fit. Each form session gets its own DO instance that accumulates events, detects the email field, triggers the abandonment alarm, and persists encrypted snapshots to D1. The alarm API means abandonment detection is just "set alarm for N seconds after last activity" — no polling, no cron.

The CORS-safelisted sendBeacon trick saved a lot of complexity. By using text/plain as the content type, the browser treats it as a "simple request" and skips the preflight OPTIONS request entirely. The Worker parses the JSON body server-side regardless.

Smart Placement handles the latency trade-off automatically. API routes that hit D1 run near the database, while static assets serve from the nearest edge.

Infrastructure cost

On the Workers paid plan ($5/mo), the included limits cover roughly 1,000 free-tier customers. The main variable cost is Resend for recovery emails. At early scale, total cost is about a coffee per month.

Try it

I'm building this solo and in public. Feedback on the architecture, the snippet's privacy approach, or the product itself is very welcome.

Top comments (0)