{
  "markdown": "# docstoc\n\nPaste your unpaid invoices. Get the follow-up email already written, in the right tone for how late it is.\n\n**docstoc never emails your clients for you** — it drafts follow-ups you copy into Gmail, Outlook, or Apple Mail. Tone escalates with days overdue (friendly → firm → direct).\n\n## Live URLs\n\n| | URL |\n|---|---|\n| **Marketing** | https://docstoc.io/ |\n| **App (Tool)** | https://docstoc.io/app/ |\n| **Login** | https://docstoc.io/app/login |\n| **Connector** | https://docstoc.io/app/connector |\n| **Admin** | https://docstoc.io/app/admin |\n| **API** | https://api.docstoc.io |\n| **MCP** | https://api.docstoc.io/mcp — read-only tools public; `draft_chase_email` requires session or API key |\n\nWorker `PUBLIC_APP_URL` is `https://docstoc.io` (magic links, OAuth callbacks, Stripe return URLs, digest links).\n\nLegacy `chasa.io` / `api.chasa.io` **301** to docstoc (Pages `_middleware.ts` + worker redirect). See `scripts/oauth-redirect-uris.md` for provider console URIs.\n\n**Go-live checklist**\n\n1. Attach `docstoc.io` on Cloudflare Pages project `chasa` and wait for **Active**.\n2. `www.docstoc.io` → apex: handled by `apps/web/functions/_middleware.ts` (redeploy web).\n3. `chasa.io` / `www.chasa.io` → `docstoc.io`: same middleware.\n4. `api.chasa.io` → `api.docstoc.io`: worker middleware in `apps/worker/src/lib/legacyHostRedirect.ts`.\n5. Register OAuth redirect URIs on `api.docstoc.io` — see `scripts/oauth-redirect-uris.md`.\n6. Optional DMARC: `./scripts/dmarc-remove-rua.sh` (removes `rua=` from `_dmarc.docstoc.io`).\n7. Run `./scripts/go-live-verify.sh`.\n\nThe order does not matter: browser-facing links are built from the origin the request came in on (see “App origin resolution” below), so pages.dev and chasa.io both keep working whichever is deployed first.\n\n### App origin resolution\n\nMagic links, post-login redirects, Stripe return URLs and team invites use the origin the user is actually on, not `PUBLIC_APP_URL`. The Pages `/api` proxy forwards it as `X-Docstoc-App-Origin`, and the worker honours it only if it matches `lib/appUrl.ts`'s allowlist (`PUBLIC_APP_URL`, `docstoc.io`, `www.docstoc.io`, legacy `chasa.io` hosts, `*.pages.dev` project/preview hosts, localhost). This keeps preview deploys self-contained and makes the domain cutover zero-downtime.\n\nRequests that arrive without a trusted origin — provider OAuth callbacks hitting `api.docstoc.io` directly, and the digest cron — still fall back to `PUBLIC_APP_URL`.\n\n---\n\n## Product specs\n\n### Plans & pricing\n\n| Plan | Price | AI drafts | Team seats | Notes |\n|------|-------|-----------|------------|-------|\n| **Free** | $0 | **5 / month** (server-enforced) | 1 (owner only) | No signup required for drafts; sign in to upgrade |\n| **Pro** | **$14.99 / mo** | Unlimited | **5** (owner + invites) | Flat workspace fee — not per-seat; most popular |\n| **Business** | **$39.99 / mo** | Unlimited | **20** | Smart reply, risk score, demand letters, cert branding, SSL automation |\n\nStripe price IDs live in `apps/worker/wrangler.toml` as `STRIPE_PRICE_SOLO` (maps to Pro), `STRIPE_PRICE_PRO` / `STRIPE_PRICE_ENTERPRISE` (map to Business; non-secret). Billing portal and webhooks update `accounts.plan` and `is_paid`.\n\n### Feature matrix (by plan)\n\n| Feature | Free | Pro+ |\n|---------|------|-------|\n| AI chase email drafts (tone by days overdue) | 5/mo | Unlimited |\n| 15+ copy-paste email templates (`/free-templates/`) | ✓ | ✓ |\n| CSV invoice upload (Tool) | ✓ | ✓ |\n| Manual invoice list + aging board (Tool) | ✓ | ✓ |\n| Rewrite softer / firmer / shorter | — | ✓ |\n| Thank-you, reply-to-client, 3-step sequence, SMS/WhatsApp drafts | — | ✓ |\n| Clients CRM + contact notes | — | ✓ |\n| Aging sync (Tool → server) | — | ✓ |\n| Reminder calendar (planned chase dates) | — | ✓ |\n| Chase timeline + mark sent/paid | — | ✓ |\n| Daily chase digest email (9 AM Eastern, approve-to-send) | — | ✓ |\n| Reminder snooze + stop when marked paid | — | ✓ |\n| Email open/click tracking on tracked HTML | — | ✓ |\n| Default payment link (Stripe, PayPal, Venmo, Zelle…) | — | ✓ |\n| Zapier templates (FreshBooks, Wave, Zoho Books) | — | ✓ |\n| Smart reply classifier + promised pay date detection | — | Business |\n| Client payment risk score | — | Business |\n| Formal demand letter + collections evidence pack | — | Business |\n| Custom certificate branding + SSL automation | — | Business |\n| Custom branding (logo, late-fee hint) | — | ✓ (admin role) |\n| Outbound webhooks (`chase.drafted`, `chase.sent`, …) + HMAC signatures | — | ✓ (admin) |\n| Click tracking (tracked copy HTML) | — | ✓ |\n| Team invites (admin / member roles) | — | ✓ |\n| Cloud storage PDF import (Dropbox, OneDrive, Box) | — | ✓ |\n| QuickBooks Online + Xero overdue import | — | ✓ |\n| HTTP API + API keys (`/api/v1/chase/draft`) | — | ✓ |\n| Zapier / Make via API key | — | ✓ |\n\n**Workspace roles:** owner is always admin. Invited members can use Pro+ features; **admin-only** actions: branding, webhooks, connector OAuth connect/import, API key CRUD, team invites.\n\n### App routes (`/app/*`)\n\n| Route | Purpose |\n|-------|---------|\n| `/` | Tool — invoice list, AI drafts, CSV/PDF import, multi-select batch |\n| `/login` | Magic-link login + Cloudflare Turnstile |\n| `/account` | Plan, billing portal, payment link |\n| `/team` | Invite members, roles (Pro+) |\n| `/clients` | Client CRM (Pro+) |\n| `/branding` | Logo + late-fee hint (workspace admin) |\n| `/webhooks` | Outbound webhook URLs (workspace admin) |\n| `/connector` | Cloud storage + QBO/Xero OAuth + API keys |\n| `/admin` | Internal admin dashboard (separate cookie) |\n\nAll app routes except `/login` and `/admin` require a session. Unauthenticated users redirect to `/login`. SPA analytics require cookie consent (GDPR).\n\n### i18n (EN + ES)\n\nSame path as Docracy: lightweight catalogs in `apps/web/app/src/lib/i18n/` (no i18next). `LocaleProvider` + EN|ES switcher on login, sidebar, and More sheet. P1 covers login / shell / welcome; expand catalogs for the rest of the app next. Preference stored as `chasa_locale`. Marketing HTML stays English until a later pass.\n\n### Marketing & SEO (static HTML in `apps/web/public/`)\n\n- Homepage with pricing, FAQ JSON-LD, competitor comparison\n- SEO landing pages: `/invoice-follow-up`, `/payment-reminder`, `/overdue-invoice`, `/chase-invoices`, `/freelancer-invoice-follow-up`\n- `/features/` (index, AI tone, templates), `/docs/`, `/ai`, `/about`\n- `/blog/` (3 articles + dynamic `post.html`)\n- `/free-templates/` — 15 generated template pages + index\n- Legal: `/privacy`, `/terms`, `/imprint`\n- `sitemap.xml`, `robots.txt`, `404.html`, cookie consent, scroll-depth analytics\n- Self-hosted fonts (no Google Fonts); enforcing CSP via `_headers`\n\nRegenerate free templates after editing `apps/web/scripts/generate-free-templates.mjs`:\n\n```bash\nnpm run generate:templates --workspace apps/web\n```\n\n---\n\n## Architecture\n\nMonorepo (npm workspaces):\n\n| Package | Stack | Deploy target |\n|---------|-------|---------------|\n| `apps/web` | React 18 + Vite + React Router | Cloudflare Pages (`chasa`) |\n| `apps/worker` | Hono + Cloudflare Workers + D1 + Workers AI | `api.chasa.io` |\n\n```\napps/web/\n  app/              React SPA (/app/*) — lazy-loaded routes, AccountProvider\n    src/pages/tool/     Tool module (components, CSV import, aging sync)\n    src/pages/connector/ Connector module (OAuth, API keys)\n  public/           Static marketing site + fonts + _headers\n  scripts/          copy-fonts, generate-free-templates, validate-static-html\n\napps/worker/\n  src/routes/       HTTP handlers (auth, billing, emails, aging, mcp, …)\n  src/lib/          auth, ai, billing, schemas (Zod), rate limits, webhooks\n  migrations/       D1 SQL (0001–0011)\n```\n\n**Data:** Cloudflare D1 (`chasa-db`). OAuth tokens encrypted at rest (AES-GCM, `TOKEN_SECRET`). Sessions in `sessions` table; magic links single-use (atomic consume).\n\n**AI:** Cloudflare Workers AI (`@cf/meta/llama-3.1-8b-instruct-fp8`). Prompts wrap user content in delimiters to reduce injection.\n\n### API surface (worker)\n\n| Prefix | Purpose |\n|--------|---------|\n| `/api/auth` | Magic link request/verify, logout, Turnstile config |\n| `/api/account` | Profile, branding |\n| `/api` (emails) | `generate-email`, `rewrite-email`, `generate-thank-you`, `generate-reply`, `generate-sequence`, `generate-sms` |\n| `/api/billing` | Stripe checkout, portal, webhook |\n| `/api/aging` | Aging board CRUD + atomic batch sync |\n| `/api/clients` | Client CRM |\n| `/api/reminders` | Chase reminder calendar |\n| `/api/team` | Workspace members + invites |\n| `/api/webhooks` | Outbound webhook CRUD |\n| `/api/connector` | Legacy API key CRUD |\n| `/api/account/connectors` | Cloud + accounting OAuth |\n| `/api/tracking` | Tracked copy + click stats |\n| `/api/analytics` | Event track + pageview aggregates |\n| `/api/v1/chase/draft` | Public HTTP API (Bearer `chasa_…` key) |\n| `/api/admin` | Admin dashboard API |\n| `/api/blog` | Public blog posts from D1 |\n| `/api/csp-report` | CSP violation reports |\n| `/mcp` | MCP JSON-RPC (draft tools, template list) |\n| `/api/t/:id` | Click redirect (allowlisted URLs only) |\n\n---\n\n## Security & compliance (implemented)\n\n- **Free-tier quota:** 5 AI drafts/month enforced in D1 (`ai_usage`), not client-side\n- **Rate limits:** AI drafts, MCP, analytics, admin login (D1 buckets)\n- **Turnstile:** fail-closed in production HTTPS when secret is set; 60s magic-link cooldown per email\n- **HKDF token purposes:** separate keys for session, magic-link, API key, admin, invite, OAuth state, webhook signing (legacy hash fallback on lookup)\n- **Session rotation:** prior sessions invalidated on magic-link login; admin sessions cleared on admin login\n- **Session cleanup:** daily cron purges expired sessions, magic links, rate-limit buckets\n- **RBAC:** workspace admin for branding, webhooks, connector OAuth, API keys, team invites\n- **Input validation:** Zod schemas on all JSON API routes (auth, emails, clients, team, webhooks, billing, admin, MCP draft args)\n- **Stripe webhooks:** event-id deduplication (`stripe_events`)\n- **Outbound webhooks:** `X-Chasa-Signature: sha256=…` HMAC\n- **Click tracking:** redirects only to URLs stored when the tracked email was created\n- **Webhook URLs:** SSRF checks (no localhost / private IPs)\n- **OAuth state:** timing-safe HMAC compare\n- **CSP:** enforcing policy on Pages; `report-uri` → `/api/csp-report`\n- **GDPR:** cookie consent gates SPA analytics; no visitor IDs on page views (aggregate only)\n- **Secrets:** never committed — `wrangler secret put …`\n\n---\n\n## Local development\n\n```bash\nnpm install\nnpm run dev:worker   # wrangler dev --local, http://127.0.0.1:8787\nnpm run dev:web      # vite, http://localhost:5173 (proxies /api to worker)\n```\n\nWithout `RESEND_API_KEY`, magic links log to the worker console — copy the URL into your browser.\n\n### Quality checks\n\n```bash\nnpm run lint          # ESLint (web app + worker)\nnpm run test          # Vitest (schemas, crypto, dates)\nnpm run typecheck     # tsc worker + vite build web\n```\n\nCI (`.github/workflows/ci.yml`) runs lint → test → static HTML validation → typecheck on every PR; **auto-deploys worker + web on push to `main`** when `CLOUDFLARE_API_TOKEN` is set.\n\n### Deploy manually\n\n```bash\nnpm run deploy:worker\nnpm run deploy:web    # builds (copies fonts) then pages deploy\n```\n\nAfter schema changes:\n\n```bash\ncd apps/worker\nwrangler d1 migrations apply chasa-db --local    # dev\nwrangler d1 migrations apply chasa-db --remote   # production\n```\n\nMigrations `0001`–`0011` include plans, admin/analytics, branding, webhooks, API keys, cloud connectors, clients/aging, parity features, and security hardening (rate limits, AI usage, Stripe idempotency, tracking link allowlist).\n\n---\n\n## Cloudflare Turnstile (bot protection)\n\nProtects `/api/auth/request` and admin login.\n\n1. [Cloudflare Dashboard](https://dash.cloudflare.com/) → **Turnstile** → **Add widget**\n2. Hostnames: `chasa-71s.pages.dev`, `chasa.io`, `localhost`\n3. Site key → `TURNSTILE_SITE_KEY` in `wrangler.toml` `[vars]`\n4. Secret → `wrangler secret put TURNSTILE_SECRET_KEY`\n5. Redeploy worker. Login UI reads config from `GET /api/auth/config`.\n\n**Local / without keys:** verification bypassed with console warning. Test keys:\n\n| | Value |\n|---|---|\n| Site key | `1x00000000000000000000AA` |\n| Secret key | `1x0000000000000000000000000000000AA` |\n\n---\n\n## One-time cloud setup\n\n1. `cd apps/worker && wrangler d1 create chasa-db` — paste `database_id` into `wrangler.toml`\n2. `wrangler d1 migrations apply chasa-db --remote`\n3. **Stripe (test mode first):** create two recurring Prices (Pro $14.99, Business $39.99). Copy `price_…` IDs into `wrangler.toml`:\n   - `STRIPE_PRICE_SOLO`, `STRIPE_PRICE_PRO`, `STRIPE_PRICE_ENTERPRISE`\n4. `wrangler secret put TOKEN_SECRET` — `openssl rand -hex 32`\n5. `wrangler secret put STRIPE_SECRET_KEY` (test key first)\n6. Stripe webhook → `https://api.chasa.io/api/billing/webhook` — events: `checkout.session.completed`, `customer.subscription.deleted` → `wrangler secret put STRIPE_WEBHOOK_SECRET`\n7. `wrangler secret put RESEND_API_KEY` — verify sending domain in Resend\n8. Turnstile — see above\n9. `wrangler secret put ADMIN_PASSWORD` — for `/app/admin`\n10. **Cloud storage OAuth (optional, Pro+):**\n\n    | Provider | Redirect URI |\n    |----------|--------------|\n    | Dropbox | `https://api.docstoc.io/api/account/connectors/dropbox/callback` |\n    | OneDrive | `https://api.docstoc.io/api/account/connectors/onedrive/callback` |\n    | Box | `https://api.docstoc.io/api/account/connectors/box/callback` |\n    | Google | `https://api.docstoc.io/api/account/connectors/google/callback` |\n\n    Google Sign-In redirect: `https://api.docstoc.io/api/auth/google/callback`\n\n    Step-by-step for each provider: [`scripts/connectors-docstoc-setup.md`](../scripts/connectors-docstoc-setup.md)\n\n    ```bash\n    wrangler secret put DROPBOX_CLIENT_ID\n    wrangler secret put DROPBOX_CLIENT_SECRET\n    wrangler secret put ONEDRIVE_CLIENT_ID\n    wrangler secret put ONEDRIVE_CLIENT_SECRET\n    wrangler secret put BOX_CLIENT_ID\n    wrangler secret put BOX_CLIENT_SECRET\n    wrangler secret put GOOGLE_LOGIN_CLIENT_SECRET\n    wrangler secret put GOOGLE_INTEGRATIONS_CLIENT_SECRET\n    ```\n\n11. **QuickBooks Online + Xero (optional, Pro+):**\n\n    | Provider | Redirect URI |\n    |----------|--------------|\n    | QBO | `https://api.docstoc.io/api/account/connectors/quickbooks/callback` |\n    | Xero | `https://api.docstoc.io/api/account/connectors/xero/callback` |\n\n    See [`scripts/connectors-docstoc-setup.md`](../scripts/connectors-docstoc-setup.md) for console steps.\n\n    ```bash\n    wrangler secret put QBO_CLIENT_ID\n    wrangler secret put QBO_CLIENT_SECRET\n    wrangler secret put XERO_CLIENT_ID\n    wrangler secret put XERO_CLIENT_SECRET\n    ```\n\n12. Cloudflare Pages project `chasa` + custom domain `chasa.io`; Worker custom domain `api.chasa.io`\n13. Switch to Stripe **live** keys/prices/webhook only after end-to-end test-mode smoke test\n\n### Local secrets (`apps/worker/.dev.vars`, gitignored)\n\n```\nTOKEN_SECRET=dev-secret-not-for-prod\nSTRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\nADMIN_PASSWORD=your-dev-admin-password\n# Optional Turnstile test keys (or omit to bypass):\n# TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA\n```\n\nStripe CLI for local webhooks:\n\n```bash\nstripe listen --forward-to localhost:8787/api/billing/webhook\n```\n\n### Staging environment\n\n```bash\nwrangler deploy --env staging\n```\n\nUses separate D1 (`chasa-db-staging`) — create and paste ID in `wrangler.toml` `[env.staging]`.\n\n---\n\n## SEO launch checklist\n\n### Automated (build)\n\n`generate-seo.mjs` runs on every `npm run build` and writes:\n\n- `sitemap.xml` — all public pages (marketing, templates, blog)\n- `robots.txt` — crawl rules + sitemap URL\n- `blog/feed.xml` — RSS for blog posts\n- `{indexnow-key}.txt` — IndexNow key file for Bing/Yandex\n- Verification meta on `index.html` / `ai.html` when env vars are set (see below)\n\nPress kit at [/press](https://chasa.io/press) — logos, boilerplate, suggested anchor text for backlinks.\n\n### Google Search Console (one-time)\n\n1. Add property `https://chasa.io` at [Search Console](https://search.google.com/search-console)\n2. Choose **HTML tag** verification → copy the `content=\"…\"` value\n3. Rebuild with the token:\n\n   ```bash\n   GOOGLE_SITE_VERIFICATION=your_token_here npm run build --workspace apps/web\n   npm run deploy:web\n   ```\n\n   Or use **DNS TXT** in Cloudflare (no rebuild needed).\n4. Submit sitemap: `https://chasa.io/sitemap.xml`\n5. URL Inspection on `/`, `/app/`, `/payment-reminder`, `/free-templates/`\n\nOptional Bing: `BING_SITE_VERIFICATION=…` same as Google.\n\nAfter deploy, ping IndexNow (Bing):\n\n```bash\ncurl -X POST https://api.indexnow.org/indexnow \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"host\":\"chasa.io\",\"key\":\"chasa-indexnow-20260727\",\"keyLocation\":\"https://chasa.io/chasa-indexnow-20260727.txt\",\"urlList\":[\"https://chasa.io/sitemap.xml\"]}'\n```\n\n### Backlinks (founder — use [/press](https://chasa.io/press))\n\nSite-side done: social links + `sameAs` schema on all pages, internal links from blog → landing pages, RSS + llms.txt.\n\nStill manual (copy from press kit):\n\n- [ ] LinkedIn company page — website field + launch post linking to `/app/` or a blog article\n- [ ] [X @DocstocHQ](https://x.com/DocstocHQ) — pin launch post\n- [ ] Product Hunt / Indie Hackers when ready\n- [ ] RELACON GmbH website cross-link to `chasa.io`\n- [ ] 3–5 freelancer communities — link to `/blog/` articles or `/free-templates/`, not just homepage\n- [ ] Email signature with `chasa.io`\n\n---\n\n## Entity\n\ndocstoc is a product of **RELACON GmbH**, Vienna, Austria. See [/imprint](https://chasa.io/imprint) and [/privacy](https://chasa.io/privacy).\n\nContact: [founder@chasa.io](mailto:founder@chasa.io)\n",
  "bytes": 17790,
  "sha": "2433f941725f793e564b0d8fa62e70f66e5335363e2f4afa8b511876e511e8f8",
  "repo_slug": "docracy1/chasa",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_docracy1_chasa_7f654181/readme"
}