Email capture: standalone Cloudflare Worker + D1 (lighter variant of #26) - #127
Merged
Conversation
Adds a quiet "stay in the loop" interest-list form to the home's closing
section. The site stays on GitHub Pages (no apex DNS migration); only the
email-capture backend lives on Cloudflare, as a small standalone Worker
backed by D1.
- worker/subscribe.ts — Worker (export default { fetch }) repackaged from
PR #26's Pages Function. Validates/normalizes the email (EMAIL_RE, 254
cap), inserts into D1, 303-redirects to ABSOLUTE site URLs (thanks/ on
success, /?subscribe_error=1 on failure). GET = health check.
- migrations/0001_interests.sql — interests table (verbatim from #26),
two reserved columns (user_id, resend_contact_id) for later.
- wrangler.toml — Worker config (main + [[d1_databases]] DB binding,
database_id PLACEHOLDER for Aaron).
- index.njk — native cross-origin <form method="POST"> (no CORS needed:
browser follows the 303 itself) in the home-close section, endpoint via
{{ site.subscribeEndpoint }}; reuses .home-cta + tokens; tasteful CSS;
kept the inline ?subscribe_error=1 error script.
- _data/site.json — subscribeEndpoint constant (default
https://subscribe.parachute.computer/).
- subscribe/thanks.njk — thank-you page (extends current base.njk).
- eleventy.config.js — ignore worker/, migrations/, wrangler.toml,
DEPLOY-subscribe.md so the static build skips backend assets.
- package.json — wrangler + @cloudflare/workers-types devDeps;
dev:worker + db:migrate scripts.
- DEPLOY-subscribe.md — "what's left for Aaron" Cloudflare checklist.
Known gap: no rate limiting yet (accepted simple-start). No secrets
beyond the D1 binding; parameterized insert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rce_path caveat - migrations: fix stale "Pages Function" comment → "subscribe Worker"; document the unpopulated `name` column + source_path-often-NULL behavior - DEPLOY-subscribe.md: note source_path is usually NULL (cross-origin Referrer-Policy strips the path); emphasize committing the real database_id out of the placeholder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Provisioned the Cloudflare side (Unforced Development account): - D1 `parachute-interests` created; migration 0001 applied (remote). - Worker `parachute-subscribe` deployed → https://parachute-subscribe.unforced.workers.dev - wrangler.toml: real database_id + pinned account_id (non-interactive deploys). - _data/site.json: subscribeEndpoint → the deployed workers.dev URL. Smoke-tested end-to-end: POST → 303 /subscribe/thanks/ → row in D1 (then deleted the test row; count=0). A custom domain (subscribe.parachute.computer) can replace the workers.dev URL later via one site.json edit + a CF custom domain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
A SIMPLE place to capture visitor emails for later sync into a Parachute vault — "start simple, just a place to capture emails." A quiet "Stay in the loop" form in the home's closing section posts to our own store.
This is the lighter variant of #26: the site stays on GitHub Pages (no apex DNS migration). Only the email-capture backend goes to Cloudflare — a standalone Worker + D1, instead of #26's Pages Functions (which would have required moving hosting to CF Pages).
How it differs from #26's Pages Function
onRequestPost: PagesFunction→ a Workerexport default { async fetch(request, env) }(worker/subscribe.ts). Same email validate/normalize (EMAIL_RE, 254 cap), source-path-from-Referer, parameterized D1 insert, two reserved columns.Locationmust be absolute (https://parachute.computer/subscribe/thanks/on success,…/?subscribe_error=1on failure). A relative path would resolve against the Worker origin.<form method="POST">; the browser follows the Worker's 303 itself and the page never reads the cross-origin response with JS, so there's no preflight and no CORS headers needed.wrangler.tomladapted for a Worker (main = "worker/subscribe.ts"+[[d1_databases]]DBbinding,database_id = "PLACEHOLDER"), notpages_build_output_dir.The form
Quiet, serif/muted block in the
home-closesection (below "Get started", above the thin footer). One email input + a Subscribe button reusing.home-cta+ existing tokens. Endpoint is a single documented constant:_data/site.json→subscribeEndpoint(defaulthttps://subscribe.parachute.computer/), referenced as{{ site.subscribeEndpoint }}. Kept the tiny inline?subscribe_error=1script that reveals a discreet error message.Files
worker/subscribe.ts,worker/tsconfig.jsonmigrations/0001_interests.sql(verbatim from V1 interest list — homepage subscribe form, Pages Function, D1 #26)wrangler.toml_data/site.jsonsubscribe/thanks.njkindex.njk(form + scoped CSS + error script)eleventy.config.js(ignoreworker/,migrations/,wrangler.toml,DEPLOY-subscribe.md)package.json(+wrangler,@cloudflare/workers-types;dev:worker/db:migrate)DEPLOY-subscribe.md("what's left for Aaron")What's left for Aaron (Cloudflare)
wrangler login→wrangler d1 create parachute-interests(pastedatabase_idintowrangler.toml) →wrangler d1 migrations apply parachute-interests --remote→wrangler deploy→ set custom domainsubscribe.parachute.computer(or update_data/site.jsonto the workers.dev URL) → smoke-test a real submission + confirm a D1 row. Full checklist inDEPLOY-subscribe.md.Verification
npx @11ty/eleventybuilds clean;_site/index.htmlform posts to the configured endpoint;_site/subscribe/thanks/index.htmlexists;worker/,migrations/,wrangler.tomlare not copied into_site/.tsc --noEmitstrict against@cloudflare/workers-types, clean) and bundles viawrangler deploy --dry-run(2.43 KiB, D1 binding recognized).style.cssresolves).Security
DB); insert is parameterized.🤖 Generated with Claude Code