Plyra is a reference implementation of an AI-assisted accounts receivable follow-up system built for small & mid-sized agencies. It connects to QuickBooks Online and Gmail, drafts contextual collection emails using a three-stage Claude pipeline, and routes every draft through an owner-approval queue before anything is sent.
This repository documents one path from product idea to working pilot. It is published as a reference implementation, not as a production-ready product.
- A three-stage AI pipeline (context extraction → strategy selection → draft generation) using the Claude API as the reasoning engine, each stage producing structured JSON consumed by the next
- Supabase Edge Functions as the backend runtime, with Row Level Security enforcing strict account isolation throughout
- OAuth integrations with QuickBooks Online and Gmail, including token refresh and encrypted credential storage
- An approval-first architecture: the system never sends an email without explicit owner action
- A realistic data model for invoice, client, contact, email thread, draft, and escalation state
- Escalation logic that adapts tone and strategy based on relationship tier, days overdue, detected blockers, and payment history
- A cron-driven escalation engine that runs every 6 hours via
pg_cron+pg_net
web/ Next.js 15 app (App Router, server components + API routes)
supabase/migrations/ Versioned PostgreSQL schema for accounts, invoices, contacts, drafts, and events
supabase/functions/
ai-pipeline/ Three-stage Claude API pipeline
qb-sync/ QuickBooks Online invoice and contact sync
qb-oauth/ QuickBooks OAuth 2.0 flow
gmail-oauth/ Gmail OAuth 2.0 flow
gmail-send/ Sends an approved draft via the Gmail API
escalation-engine/ Advances escalation stage; runs on a 6-hour cron
instant-value/ First-run analysis shown on initial connection
_shared/ Shared token refresh helpers and AI prompt constants
See docs/architecture.md for component descriptions and request flow diagrams.
- Node.js 18+
- Supabase CLI
- A Supabase project (free tier is sufficient for development)
- A QuickBooks Developer account with a sandbox app
- A Google Cloud project with the Gmail API enabled and OAuth 2.0 credentials configured
- An Anthropic API key
Copy .env.example to web/.env.local and fill in the values.
The web app reads variables from .env.local. Edge functions read secrets set via the Supabase CLI — they do not use .env.local:
supabase secrets set ANTHROPIC_API_KEY=...
supabase secrets set QB_CLIENT_ID=... QB_CLIENT_SECRET=...
supabase secrets set QB_REDIRECT_URI=... QB_STATE_SECRET=...
supabase secrets set QB_ENVIRONMENT=sandbox
supabase secrets set GMAIL_CLIENT_ID=... GMAIL_CLIENT_SECRET=...
supabase secrets set GMAIL_REDIRECT_URI=... GMAIL_STATE_SECRET=...
supabase secrets set APP_URL=https://your-app.vercel.app
supabase secrets set AI_PIPELINE_INTERNAL_KEY=...SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are automatically injected into edge functions by Supabase and do not need to be set as secrets.
- Clone the repo
cd web && npm install- Create a Supabase project and link it:
supabase link --project-ref <ref> - Enable the
pg_cronandpg_netextensions in your Supabase dashboard under Database → Extensions before running migrations (required for the escalation cron — see note insupabase/migrations/008_escalation_cron.sql) - Apply migrations:
supabase db push - Deploy edge functions:
supabase functions deploy - Set edge function secrets (see above)
- Fill in
web/.env.local cd web && npm run dev
Seed/demo data: intentionally not included in the public repo.
This codebase was built for a small pilot and makes explicit tradeoffs:
| Area | Status |
|---|---|
| Automated CI | No CI pipeline. Unit tests exist for key edge functions but are not wired to a CI runner. |
| Prompt sync | supabase/functions/_shared/prompts.ts is a manual copy of web/lib/prompts.ts. There is no automated sync — update both files together. |
| Rate limiting | API routes and edge functions have no rate limiting layer. |
| Gmail OAuth scope | OAuth requests OpenID, email, and Gmail send-only access (gmail.send). The app does not request inbox read or full mailbox scope. |
| QuickBooks API limits | The codebase has not been tested against production QuickBooks API rate limits at scale. |
| Multi-team / multi-org | RLS enforces per-account isolation, but team membership and role management are out of scope. |
| Open/click tracking | Intentionally excluded from v1 scope. |
| Auto-send | Disabled by design. The system always requires owner approval before sending. |
- Single prompt source of truth: the prompt definitions currently live in both
web/lib/prompts.tsandsupabase/functions/_shared/prompts.ts. In a v2, I would eliminate that duplication so prompt changes cannot drift across runtimes. - Earlier send-state hardening: the approval-first workflow was the right product constraint, but I would formalize send-state transitions, event logging, and delivery integrity earlier in the build so draft lifecycle rules are explicit from day one.
- Cleaner separation between pilot pragmatism and reusable platform design: this repo optimizes for a working pilot. In a v2, I would isolate product-specific assumptions earlier so the core workflow engine is easier to reuse across industries and AR motions.
Coming soon — link will appear here when published.