Skip to content

About

Client-operations platform for a small agency: pipeline, proposals, contracts, Stripe billing, onboarding, automated monthly reporting with a human approval queue. Cloudflare Workers + D1.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Agency Ops

Client operations for a small marketing agency, from first contact to the monthly report: a sales pipeline, proposals and contracts, e-signature, Stripe checkout, onboarding, and an automated monthly report whose AI-written summary waits for a person to approve it.

It runs on Cloudflare Workers with a D1 (SQLite) database. A fresh clone runs locally with npx wrangler dev and no accounts or keys. Every outside service has a mock, and the demo data is invented.

Pipeline board

What it does

  1. Pipeline. Deals move new → consult → proposal → contract → paid → onboarding → active, one stage at a time. Any deal not yet paid can be marked lost with a reason. A deal only counts as won once it's paid: no one can drag a deal to paid. Only a confirmed payment moves it there.
  2. Proposals and contracts. A proposal is generated from the services on the deal, and a contract is generated from the accepted proposal. The contract is sent for e-signature through an adapter. A mock signing page is included, and the interface is documented so DocuSign or similar can be added.
  3. Billing. Once the contract is signed, the client gets a Stripe Checkout link (test mode only) for the first invoice: one-time fees plus the first month of monthly services. The Stripe webhook is what marks the deal paid. Without a Stripe key, a mock checkout goes through the same code path.
  4. Onboarding. A form that asks only the questions for the services the client bought, plus a checklist of the platform access the agency needs (Analytics, Search Console, Ads, the CMS…), each with a status: requested, granted, blocked or not needed. A client can't go active until the form is submitted and every access item is granted or not needed.
  5. Monthly reports. A cron job on the 2nd of each month pulls last month's numbers through an analytics adapter, builds an HTML report per active client (with a print stylesheet for PDF), and drafts a short summary with the Anthropic API, using Claude Haiku 5.5 by default. A hard per-client monthly cost cap applies (50 cents by default, roughly 300 worst-case summaries). Without an API key, a mock writes the summary.
  6. Review & Approve. Every AI draft (report summaries and suggested SEO fixes) sits in a queue until a person approves, edits or rejects it. The client's copy of a report only ever contains approved text, and a report can't be sent until its summary is approved.
  7. Audit log. Every stage change, document status change, payment, approval and report send is recorded with who did it and when.
Deal detail Onboarding
Deal detail Onboarding form and access checklist
Approval queue Report preview (drafts marked)
Approval queue Report preview

Why it's built this way

Each of these has a short decision record in docs/decisions/.

  • Paid before won (0003). "Won" should mean money received. The rule is enforced three times: in the stage rules, in the API, and by a database CHECK constraint that rejects a won stage without a won_at date. Webhook events are recorded by ID so repeats are ignored, and a payment for the wrong amount, or for a deal already marked lost, is flagged for review instead of guessed at.
  • Adapters with mocks (0002). E-signature, payments, analytics and the AI model each sit behind a small interface. Mocks run the same handlers the real webhooks do, so the demo exercises the real logic.
  • A human approval gate (0004). Models can misread numbers. The original draft is kept unchanged next to what was approved, so it's always clear what the AI wrote and what a person signed off.
  • A hard AI cost cap (0005). Before each call, the worst-case cost is reserved in a ledger with a single SQL statement that refuses to go over the cap. Afterwards it's settled to the real cost from the API's reported usage. The default model is the cheap claude-haiku-5-5, so the default 50-cent cap leaves plenty of room. Set AI_MODEL to use another priced model.
  • One Worker for API and UI (0001), and demo mode locally, a token in production (0006).
  • Documents rendered from stored data (0007), so price changes never rewrite an old contract.

Stack

  • Runtime: Cloudflare Workers, TypeScript, Hono for routing, Zod for request validation
  • Database: Cloudflare D1 (SQLite), plain SQL migrations in migrations/
  • Frontend: plain HTML, CSS and JavaScript in public/, served by the same Worker (Workers Static Assets). No build step.
  • Scheduling: Workers Cron Trigger
  • Payments: Stripe Checkout REST API (test mode only), webhook signatures checked with Web Crypto
  • AI summary: Anthropic Messages API via the official TypeScript SDK, with a JSON schema for the reply
  • Tests: Vitest running inside the Workers runtime (@cloudflare/vitest-plugin) against a real local D1. Optional browser smoke test with Puppeteer.

Run it locally

You need Node.js 20 or newer.

npm install
npx wrangler dev

Open http://localhost:8787. On the first request, demo mode creates the local database and loads the invented seed data: seven businesses, with deals spread across the pipeline. Nothing else needs configuring.

Things to try:

  1. Pipeline → Wren Street Dental (in contract). Try to move it to paid: you can't. Open the signing page and sign, then Take payment, then Pay on the mock checkout. The deal becomes paid, and the activity log shows the payment did it.
  2. Wren Street Dental → Onboarding. Answer a question and the deal moves to onboarding. Compare with Harbor Point Physical Therapy, which bought different services and gets different questions and access items.
  3. Reports → Run reports now for last month. Then open Approvals: the AI summary and an SEO suggestion are waiting. Edit and approve the summary, then send the report from its page. Client copy shows only approved text.
  4. Switch Acting as in the header to see actions recorded under a different person in the Audit log.

Optional settings go in .dev.vars (copy .dev.vars.example): a Stripe test key, an Anthropic API key, or an ADMIN_TOKEN for trying the app with demo mode off.

Database scripts, if you'd rather manage the local database yourself:

npm run db:migrate   # apply migrations to the local D1
npm run db:seed      # load the invented demo data (safe to run twice)
rm -rf .wrangler     # start over with an empty local database

Tests

npm test          # 114 tests, run inside the Workers runtime against a local D1
npm run typecheck

They cover the core rules: stage transitions and the paid-before-won rule (including the database constraint and concurrent moves), proposal → contract → payment, Stripe webhook signatures (forged, tampered, replayed and duplicated events), onboarding questions and the go-active gate, the cost cap (including simultaneous reservations), the approval gate, the cron trigger, and access rules for demo mode versus production.

An optional browser test clicks through the main flows in Chrome. Start a fresh dev server first:

rm -rf .wrangler && npx wrangler dev   # terminal 1
npm run test:ui                        # terminal 2 (set CHROME_PATH if Chrome isn't in the default macOS location)

Deploy

  1. Log in and create the database:
    npx wrangler login
    npx wrangler d1 create agency-ops
    Copy the database_id it prints into the env.production.d1_databases entry in wrangler.jsonc.
  2. Apply the migrations to the remote database:
    npm run db:migrate:remote
  3. Set the secrets. The token is required; the rest are optional and fall back to the mocks:
    npx wrangler secret put ADMIN_TOKEN --env production
    npx wrangler secret put STRIPE_SECRET_KEY --env production       # sk_test_... only
    npx wrangler secret put STRIPE_WEBHOOK_SECRET --env production
    npx wrangler secret put ANTHROPIC_API_KEY --env production
  4. Deploy:
    npm run deploy
  5. If you set a Stripe key, add a webhook in the Stripe dashboard (test mode) pointing to https://<your-worker>/api/webhooks/stripe, sending the checkout.session.* events.

The production environment has demo mode off, so every API call needs Authorization: Bearer <ADMIN_TOKEN>. The production database starts empty; load demo data with npx wrangler d1 execute DB --remote --env production --file seed/seed.sql if you want it. The browser UI is built for demo mode and doesn't send the token, so a production deployment that people use through the UI should put Cloudflare Access in front of it. See decision 0006.

What's mocked and what's real

Part In this repo Notes
Pipeline, stage rules, audit log Real
Proposals and contracts Real HTML documents rendered from stored data
E-signature Mock Interface documented in docs/adapters.md for DocuSign or similar
Payments Real (Stripe test mode) or mock Mock when no key is set. Live keys are refused.
Onboarding form and checklist Real Filled in by the agency; there's no separate client portal
Website analytics Mock Realistic GA4 / Search Console-style numbers, stable per client and month
AI summary Real (Anthropic API) or mock claude-haiku-5-5 by default. Mock when no key is set. The cost cap and approval queue apply either way.
Monthly schedule Real Cron trigger, plus a "run now" button (demo mode or token only)
Sending a report Partly "Send" records the send and locks the report. Email delivery isn't wired up.
Sign-in Minimal Demo users locally, one admin token in production
Billing after the first invoice Not included Monthly renewals would use Stripe subscriptions

Project layout

src/
  index.ts            routes + cron entry point
  domain/             pure rules: stages, pricing, cost cap, onboarding answers, documents, report HTML
  db/                 queries and actions (each change written with its audit entry)
  adapters/           esign/ payments/ analytics/ llm/ (interface, mock, real)
  routes/             HTTP handlers
  jobs/               monthly reports job
migrations/           D1 schema, applied in order
seed/seed.sql         invented demo data
public/               the frontend
test/                 Vitest suites
scripts/ui-smoke.mjs  optional browser click-through
docs/                 decision records, adapter guide, screenshots

License

MIT

About

Client-operations platform for a small agency: pipeline, proposals, contracts, Stripe billing, onboarding, automated monthly reporting with a human approval queue. Cloudflare Workers + D1.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages