Cloudflare Worker that captures quiz emails durably, logs everything, and forwards to Shopify Customer API. Edge-anycasted, free tier, zero email loss.
Every quiz email submission goes through one robust pipe instead of relying on a single VPS that could go down. Quiz form → Cloudflare Worker (durable capture in KV) → Shopify Customer API (creates customer with marketing consent + quiz answers as metafields) → existing downstream flows take over.
Why Shopify is the destination, not GHL: Shopify is the system of record. Customer record lives there. GA4 events fire from Shopify automatically. Klaviyo / Shopify Email / abandoned cart / segments all key off the customer record. Quiz answers as metafields enable A/B test analysis. One less SaaS to pay for.
Worker writes every email to Cloudflare KV the instant it arrives. 90-day TTL. Survives any downstream outage.
Worker creates Shopify Customer with accepts_marketing: true, tags, and quiz answers as metafields.
Update bz-quiz.js on UK theme to POST to the Worker. Optional capture-on-type for partial saves.
Same pattern for US/DE/FR. Worker routes to correct Shopify store based on market field.
| Failure mode | Current (Coolify Python relay) | New (Cloudflare Worker) |
|---|---|---|
| Coolify VPS down | All emails lost | Captured to KV |
| Shopify Admin API throttled | Silent failure | KV retry queue |
| Mobile network blip | ~500ms VPS, often timeout | ~30ms edge, rare timeout |
| User closes tab on email gate | Email lost | Capture-on-type saves it |
| Replay after outage | Impossible (no record) | Read KV, batch-forward |
| Audit trail | Coolify logs (rotate) | 90-day KV record per submission |
Shopify customer creation auto-fires sign_up event in GA4. Already wired through your existing GA4 setup.
Customer record with accepts_marketing: true enters Shopify Email and Klaviyo flows automatically.
Responses saved as customer metafields. Query in Shopify, segment for A/B tests, build cohorts.
Capture-on-type saves emails the moment they're typed. Non-completers get a different email flow tagged quiz_partial.
UTM and quiz variant captured per submission. Compare email-capture rates across Variants A/B/C.
Per-submission KV record with timestamp, market, consent state — for GDPR / ICO requests.
From the moment a user types their email on a Shopify quiz page, here's exactly what happens. Each stage is independent — failures at any stage don't lose the email.
Cloudflare KV is highly redundant; failure is rare. Worker returns 500. Quiz form retries client-side. Email captured on retry.
KV record stays with forward_status: failed. Cron Trigger retries hourly. After 5 retries: forward_dead for manual review.
Treated as success. Existing customer gets metafields updated with new quiz response. No duplicate created.
Capture-on-type already saved partial email with status_hint: partial. Different email flow targets these.
| Data | Where | Purpose | Retention |
|---|---|---|---|
| Quiz submission record | Cloudflare KV (submissions:<id>) |
Durable audit trail, replay queue | 90 days |
| Customer profile | Shopify Customer record | System of record for marketing | Forever |
| Quiz answers | Shopify Customer metafields | Segmentation + A/B analysis | Forever |
| Marketing consent | Shopify email_marketing_consent |
GDPR / compliance | Forever |
| UTM + variant | KV record + Shopify customer note | Attribution | 90 days KV / forever Shopify |
| Failed forwards queue | Cloudflare KV (failed_forwards:<id>) |
Retry buffer | Until resolved or 5 retries |
The Worker architecture is locked. These are the tunable choices that affect what data flows where, who gets emailed, and how aggressive the capture is.
| Option | Pros | Cons | Verdict |
|---|---|---|---|
| Shopify Customer API | System of record. Triggers everything. GA4 events automatic. Metafields = analytics. One vendor. | Rate limit 2/sec per store (well within quiz volume). | RECOMMENDED |
| GHL inbound webhook | Existing nurture workflows already built. | Extra SaaS cost. Doesn't trigger Shopify flows. No GA4 attribution. | Skip |
| Both (Shopify + GHL) | Belt-and-braces. Lose nothing. | Double-handling of emails. Two systems to keep in sync. Pay GHL twice. | Only if GHL has unique flows worth keeping |
| Klaviyo direct | Specialised email tool. Powerful segments. | Bypasses Shopify customer record — loses central data hub. | Skip |
| Option | Behaviour | Captures | Verdict |
|---|---|---|---|
| Submit-only | Email saved when user clicks submit on email gate. | Completers only. | Misses ~30% who bail |
| Capture-on-type + submit | Debounced 800ms POST when valid email format typed. Full submit on click. | Completers AND valid-email-typers who bail. | RECOMMENDED |
| Aggressive (every keystroke) | POST on every input change. | Most. | Spammy, hits rate limits |
Quiz form sends market: "uk" | "us" | "de" | "fr". Worker routes to the correct Shopify store using per-market admin tokens.
| Market | Shopify store | Worker env var (admin token) | Status |
|---|---|---|---|
| UK | brainzyme.myshopify.com |
UK_SHOPIFY_ADMIN_TOKEN |
Token in .env |
| US | brainzyme-us.myshopify.com |
US_SHOPIFY_ADMIN_TOKEN |
Token in .env |
| DE | brainzyme-de.myshopify.com |
DE_SHOPIFY_ADMIN_TOKEN |
Token in .env |
| FR | brainzyme-fr.myshopify.com |
FR_SHOPIFY_ADMIN_TOKEN |
Token in .env |
Each quiz response becomes a customer metafield. Suggested namespace: quiz. Example for Brain Fog Quiz UK Variant A:
POST /admin/api/2024-04/customers.json
{
"customer": {
"email": "[email protected]",
"accepts_marketing": true,
"tags": "quiz-completed, brain-fog-uk-a, variant-a",
"note": "Brain Fog Quiz UK A | UTM: google/cpc/brainfog-q2",
"metafields": [
{ "namespace": "quiz", "key": "id", "type": "single_line_text_field", "value": "brain-fog-uk-a" },
{ "namespace": "quiz", "key": "variant", "type": "single_line_text_field", "value": "a" },
{ "namespace": "quiz", "key": "primary_concern", "type": "single_line_text_field", "value": "foggy mornings" },
{ "namespace": "quiz", "key": "result", "type": "single_line_text_field", "value": "focus_pro" },
{ "namespace": "quiz", "key": "completed_at", "type": "date_time", "value": "2026-05-04T..." }
]
}
}
Live state of the Quiz Bridge build. Each phase is independent — later phases ship value even if earlier are incomplete.
tools/cloudflare_worker_reviews.js)/api/reviews + /api/quiz-submit + /api/submissions.env)white-flower-17a2-brainzyme-reviews)brainzyme_reviews created and bound as STATE_KVWRITE_TOKEN secret added (encrypted)cf_country auto-stamped, 90-day TTL active.env as CLOUDFLARE_REVIEWS_WORKER_URLUK_SHOPIFY_ADMIN_TOKEN)SHOPIFY_STORES (UK live, US/DE/FR stubbed)forwardToShopify() function with full error classification (retryable vs dead)/api/quiz-submit: KV write FIRST, then forward, then update statusfailed_forwards:* retry queue + cron-driven hourly retry (max 5 attempts)quiz: id, submission_id, captured_at, status_hint, variant, market, responses, utm)wrangler.toml (preserves across deploys)sign_up event fires from new customer creation (during Phase 3)assets/bz-quiz-relay.js) — already wired into bz-quiz.js engine, just pointed at dead Coolify endpointDEFAULT_ENDPOINT from relay.nutritionalproducts.org/quiz/submit (503) to Cloudflare Worker /api/quiz-submit178073469309) via Shopify Admin API — bypassed git/drift hook140966985920 · DE:178768019720 · FR:179437175126) — same checksum across all 4/api/test-stores returns OK for UK/US/DE/FRmemory/feedback_quiz_bridge_canonical.mdOnce you've deployed the Worker (Phase 1), enter the URL + bearer token below. The dashboard will fetch live submission data from KV. Token is the bearer secret you set as WRITE_TOKEN in the Worker.
Stored in browser localStorage. Never sent anywhere except your Worker.
Every customer-facing data capture (quiz emails, signups, contact forms) sits at the front of every funnel. Losing one is direct revenue loss. Edge-first capture makes loss almost impossible by design.
The classic mistake: a single endpoint that validates, processes, AND writes the email. If any step fails, the email is gone. The robust pattern: write the email durably first, then attempt processing. If processing fails, the data still exists and is replayable.
// Bad: email lost if any step fails
async function handleSubmit(req) {
const data = await req.json();
await validateEmail(data.email); // could throw
await sendToCRM(data); // could throw
await sendConfirmationEmail(data.email); // could throw
return { ok: true };
}
// Good: email captured durably first
async function handleSubmit(req) {
const data = await req.json();
await env.KV.put(`sub:${id}`, JSON.stringify(data)); // ALWAYS happens
// Below can fail freely; data already saved
try { await sendToCRM(data); } catch { logForRetry(); }
try { await sendConfirmationEmail(...); } catch {}
return { ok: true };
}
| Property | Cloudflare KV | Postgres / MySQL | Why it matters here |
|---|---|---|---|
| Latency | ~5ms (edge) | ~50-200ms (regional) | Quiz POST feels instant on mobile |
| Cost | Free for 1k writes/day | $5-20/mo minimum | Quiz volume well under free tier |
| Setup | Click "Create Namespace" | Provision DB, schema, migrations | Ship today, not next week |
| Uptime | ~99.99% (anycast edge) | ~99.95% (single region) | Higher floor on capture reliability |
| Schema | JSON blob, evolves freely | Migrations needed | Quiz schema changes monthly without DBA |
| Query | Key prefix list | SQL | For analytics, sync to Shopify metafields (source of truth) |
Not needed. Shopify is the source of truth post-forward. KV is just the durable log.
Quiz answers as Shopify metafields are queryable via Shopify Admin API + analytics tools.
Shopify Email handles welcome flow. Worker doesn't need its own email infrastructure.
Quiz submissions are anonymous by design (pre-signup). Bearer token only for admin reads.
This Worker is the foundation for an entire Brainzyme edge data plane. Same pattern works for: contact forms, sample requests, partner inquiries, abandoned-cart emails, quiz answer A/B test events, GA4 server-side measurement protocol, and (in Phase 2 of the broader bridge) triggering local Claude Code tasks. One Worker, evolving route surface. Zero per-form infrastructure.
Spec: Claude OS Dashboard · GitHub · Worker code at F:/Claude Root/tools/cloudflare_worker_reviews.js · Spec doc at F:/Claude Root/claude-setup/specs/2026-05-01-webhook-to-claude-cli-bridge-design.md