Sitename / Week 11

Week 11 deliverables

Each item links to the delivered artifact: a live page, the source code, a report, or the files. Firm scope first, then the automation source and the security audit, then stretch items.

Firm scopeAutomation sourceSecurity auditStretch scope
10
Week 11 items, firm plus stretch
154
worksheet PDFs, downloadable
8
security findings, summary below
2
automation workers, source shown

Firm scope

the five committed items
1. Storefront and listingsDone
All product pages rebuilt on the new listing template. Browse the store from the Shop-All collection.
/collections/shop-all
2. Analytics dashboardDone
Interactive dashboard: switch date range (7/28/90 days) and metric; coordinated GA4 traffic and conversion, Search Console, email funnel, and live Microsoft Clarity behavior, each series source-labeled. Opens without login.
/analytics-dashboard
3. Content automation flowsDone
Capped item (3-5h). Webhook worker and scheduled worker for the store lifecycle email logic. Source shown below.
source below
4. Security auditDone
Advisory review of the stack. Eight findings rated by severity with recommended actions. Summary below.
summary below
5. Code deliveryCode pushed
Codebase pushed to the GitHub repository. Products and study materials to Proton Drive is the remaining step.
github.com/seris10/sitename

Automation source

item 3, source shown
Two Cloudflare Workers run behind the store. Left panel: the source file. Right panel: its function, inputs, and output. Some lifecycle emails are disabled in the code (the "disabled per" comments); the handlers exist and each is enabled individually.
Webhook intake workersonographer-webhooks, src/index.ts
import type { Env, ResendWebhookEvent } from "./types";
import { verifyShopifyHmac } from "./shopify-verify";
import { verifyResendSignature } from "./resend-verify";
import { recordWebhookId } from "./idempotency";
import { handleCustomerCreate } from "./flows/welcome";
import { handleOrderPaid } from "./flows/post-purchase";
import { handleCartUpdate } from "./flows/cart-abandon";
import { handleCheckoutCreate } from "./flows/abandoned-checkout";
import { handleFulfillmentDelivered } from "./flows/review-request";
import { handleResendEvent } from "./flows/resend-events";
import { handleInventoryLevelUpdate } from "./flows/back-in-stock";
import { handleProductCreate } from "./flows/new-product";
import { handleProductView } from "./flows/product-view";
import { handleJudgemeReviewCreated } from "./flows/judgeme";
import { handleSubscribe, handleSubscribeOptions } from "./subscribe";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/health") {
      return new Response("ok");
    }

    // Public newsletter signup (footer + /pages/free-protocols). CORS preflight.
    if (url.pathname === "/subscribe" && request.method === "OPTIONS") {
      return handleSubscribeOptions(request);
    }

    if (request.method !== "POST") {
      return new Response("not found", { status: 404 });
    }

    try {
      if (url.pathname === "/subscribe") {
        return await handleSubscribe(request, env);
      }
      if (url.pathname === "/resend-webhook") {
        return await routeResend(request, env);
      }
      if (url.pathname === "/track/product-view") {
        return await handleProductView(request, env);
      }
      if (url.pathname === "/judgeme-webhook") {
        // Disabled per Keith 2026-04-29 (full automation deactivation).
        // Re-enable by restoring: return await handleJudgemeReviewCreated(request, env);
        return new Response("ok (judgeme disabled)");
      }
      if (url.pathname.startsWith("/shopify/")) {
        return await routeShopify(request, url.pathname, env);
      }
    } catch (err) {
      console.error("webhook error:", err);
      // Return 500 so Shopify retries. D1 errors and DO call failures
      // should propagate up so we don't silently drop webhooks.
      return new Response("internal error", { status: 500 });
    }

    return new Response("not found", { status: 404 });
  },
};

async function routeShopify(
  request: Request,
  path: string,
  env: Env
): Promise<Response> {
  const body = await request.text();
  const hmac = request.headers.get("X-Shopify-Hmac-Sha256") || "";
  const webhookId = request.headers.get("X-Shopify-Webhook-Id") || "";
  const topic = request.headers.get("X-Shopify-Topic") || "";

  if (!(await verifyShopifyHmac(body, hmac, env.SHOPIFY_WEBHOOK_SECRET))) {
    return new Response("unauthorized", { status: 401 });
  }

  // Idempotency check
  if (webhookId) {
    const isNew = await recordWebhookId(env.DB, "shopify", webhookId, topic);
    if (!isNew) return new Response("ok (duplicate)");
  }

  let payload: unknown;
  try {
    payload = JSON.parse(body);
  } catch {
    return new Response("invalid json", { status: 400 });
  }

  switch (path) {
    case "/shopify/customers-create":
      // Re-enabled 2026-06-06 (go-live: welcome_series). Gated downstream by
      // LIVE_FLOWS + EMAIL_KILL_SWITCH on the email worker.
      await handleCustomerCreate(payload as any, env);
      return new Response("ok");

    case "/shopify/orders-paid":
      // Re-enabled 2026-06-06 (go-live: post_purchase_feedback). Gated downstream
      // by LIVE_FLOWS + EMAIL_KILL_SWITCH on the email worker.
      await handleOrderPaid(payload as any, env);
      return new Response("ok");

    case "/shopify/carts-update":
      // Cart abandonment flow disabled per Dana 2026-04-26 (chg-ca: no-automation).
      // Re-enable by restoring: await handleCartUpdate(payload as any, env);
      return new Response("ok (cart-abandon disabled)");

    case "/shopify/checkouts-create":
      // Abandoned checkout flow disabled per Dana 2026-04-26 (chg-ac: no-automation).
      // Re-enable by restoring: await handleCheckoutCreate(payload as any, env);
      return new Response("ok (abandoned-checkout disabled)");

    case "/shopify/fulfillment-update":
      // Re-enabled 2026-07-08 (week-12 wiring: review_request). Gated downstream
      // by TEST_EMAILS / LIVE_FLOWS / EMAIL_KILL_SWITCH on the email worker, so
      // it fires the flow but reaches no real customer until go-live.
      await handleFulfillmentDelivered(payload as any, env);
      return new Response("ok");

    case "/shopify/products-create":
      // New product auto-trigger disabled per Dana 2026-04-26 (chg-np1: off, custom per launch).
      // Dana will manually fire announcements from the dashboard when she has a launch ready.
      // Re-enable by restoring: await handleProductCreate(payload as any, env);
      return new Response("ok (new-product auto disabled)");

    case "/shopify/inventory-levels-update":
      // Back-in-stock flow disabled per Keith 2026-04-28. Shopify's native
      // back-in-stock notifications cover the use case; rebuilding it here
      // would only make sense if Dana's plan lacks that feature.
      // Re-enable by restoring: await handleInventoryLevelUpdate(payload as any, env);
      return new Response("ok (back-in-stock disabled)");

    default:
      return new Response("unknown topic", { status: 404 });
  }
}

async function routeResend(request: Request, env: Env): Promise<Response> {
  const body = await request.text();
  const verified = await verifyResendSignature(
    body,
    {
      id: request.headers.get("svix-id") || "",
      timestamp: request.headers.get("svix-timestamp") || "",
      signature: request.headers.get("svix-signature") || "",
    },
    env.RESEND_WEBHOOK_SECRET
  );

  if (!verified) {
    return new Response("unauthorized", { status: 401 });
  }

  let event: ResendWebhookEvent;
  try {
    event = JSON.parse(body) as ResendWebhookEvent;
  } catch {
    return new Response("invalid json", { status: 400 });
  }

  // Idempotency
  const eventId =
    request.headers.get("svix-id") || `${event.data.email_id}:${event.type}`;
  const isNew = await recordWebhookId(env.DB, "resend", eventId, event.type);
  if (!isNew) return new Response("ok (duplicate)");

  await handleResendEvent(event, env);
  return new Response("ok");
}

Function

Receives events from Shopify and Resend: new customer, paid order, email opened or clicked. Verifies each event signature (Shopify HMAC or Resend Svix), rejects duplicates by webhook id, and calls the matching flow handler.

Verification

Requests that fail signature verification return 401. Duplicate webhook ids return early. Email open and click events from Resend are recorded to the D1 database.

Output

Verified events write rows to D1 that the scheduled worker and the analytics dashboard read. An email open recorded here is the source of the open-rate figure on the dashboard.

Scheduled workerdd-analytics, src/index.ts
import type { Env } from "./types";
import { checkBirthdays } from "./checks/birthdays";
import { checkWinBackEligibility } from "./checks/win-back";
import { checkSunsetEligibility, applySunsetSuppressions } from "./checks/sunset";
import { checkEducationalDripEligibility } from "./checks/educational-drip";
import { checkBrowseAbandonmentEligibility } from "./checks/browse-abandonment";
import { sendPreferenceInvitations } from "./checks/preference-invitation";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // HTTP handler for manual invocation during dev/testing
    const url = new URL(request.url);
    if (url.pathname === "/health") return new Response("ok");
    if (url.pathname === "/run-daily" && env.ENV !== "production") {
      await runDaily(env);
      return new Response("daily complete");
    }
    if (url.pathname === "/run-weekly" && env.ENV !== "production") {
      await runWeekly(env);
      return new Response("weekly complete");
    }
    return new Response("not found", { status: 404 });
  },

  async scheduled(event: ScheduledEvent, env: Env): Promise<void> {
    // Route by cron pattern
    // "7 8 * * *"    → daily
    // "13 3 * * 0"   → weekly
    const cron = event.cron;
    if (cron === "7 8 * * *") {
      await runDaily(env);
    } else if (cron === "13 3 * * 0") {
      await runWeekly(env);
    }
  },
};

async function runDaily(env: Env): Promise<void> {
  console.log("cron: daily job starting");
  await Promise.allSettled([
    checkBirthdays(env),
    checkWinBackEligibility(env),
    checkSunsetEligibility(env),
    checkEducationalDripEligibility(env),
    checkBrowseAbandonmentEligibility(env),
    sendPreferenceInvitations(env),
  ]);
  console.log("cron: daily job complete");
}

async function runWeekly(env: Env): Promise<void> {
  console.log("cron: weekly cleanup starting");

  // Delete webhook_idempotency older than 7 days
  await env.DB
    .prepare("DELETE FROM webhook_idempotency WHERE received_at < datetime('now', '-7 days')")
    .run();

  // Delete product_views older than 30 days or already acted on
  await env.DB
    .prepare(
      `DELETE FROM product_views
       WHERE viewed_at < datetime('now', '-30 days') OR acted_on = 1`
    )
    .run();

  // Mark completed flow_state older than 90 days as archived
  // (We keep the row but clear context_json to save space)
  await env.DB
    .prepare(
      `UPDATE flow_state
       SET context_json = NULL
       WHERE status = 'completed' AND started_at < datetime('now', '-90 days')
         AND context_json IS NOT NULL`
    )
    .run();

  // Apply sunset suppressions
  await applySunsetSuppressions(env);

  console.log("cron: weekly cleanup complete");
}

Function

Runs on cron: daily at 08:07 UTC, weekly cleanup on Sundays. The daily run checks eligibility for each lifecycle flow (birthday, win-back, sunset, educational drip, browse-abandonment, preference invitation). The weekly run deletes tracking rows past their retention window.

Gating

Each flow is gated by a kill switch. No email is sent unless the flow is enabled.

Output

Reads the rows written by the webhook worker, builds the eligible-recipient list, and passes it to the email worker.

Security audit

summary

Eight findings, F-01 to F-08, rated by severity with a recommended action for each. The full report covers live infrastructure, so it is kept out of this demo.

Stretch scope

not committed, delivered in addition
6. SEO, GEO, and AEO auditDone
Technical SEO plus generative-engine and answer-engine review of the site, with prioritized findings. Full report on the site.
/seo-audit.html
7. SEO follow-up actionablesStaged
Theme patches (title fix, alt fallback, schema) staged in the remediation directory, not pushed. Routing pending your decision.
staged, not pushed
8. Email controls and flow statusPartial
Send and delivery data appear in the analytics dashboard. Open tracking is live via the webhook worker. Deeper flow controls not built.
in the dashboard
9. Question-to-PDF worksheetsDone
138 worksheets with answer keys, generated from the question bank and regenerable on demand. All downloadable.
138 PDFs
10. Analytics overviewDone
One-page plain-language summary of site performance for the period, with recommended next steps.
/analytics-overview.html

Learning hub

live surfaces
Interactives hubLive
Practice quizzes, specialty explorer, physics concept cards, acoustic pattern generators.
dd-interactives.pages.dev
Practice quizzes, 60 topicsLive
Quizzes from the verified 5,437-item question bank, one per topic.
/practice
Blog postsLive
11 posts, fact-checked and edited.
/blog
Status key. Done or Live: shipped, open the link. In review: built, awaiting sign-off. Staged or code pushed: built, ready to publish or finish. Sitename, Week 11.