Tally 💰
Hackathon: H0: The Hackathon Zero
Live Demo: gotally.vercel.app
Inspiration
Independent agencies, freelancers, and small B2B shops bleed time chasing overdue invoices. The numbers from the SMB-finance world are brutal — the average freelance designer has 22% of their invoiced revenue past-due at any moment, and the median agency owner spends 4.5 hours per week writing chase emails. Every overdue payment is an awkward email no one wants to write: too soft and the client ignores it; too hard and you torch a relationship over $1,400.
Worse, the people best at the actual work — designers, copywriters, accountants, consultants — are worst at this collection-by-email loop. They wait too long, then over-correct and send something that reads like a debt collector, and now there's a relational tax on top of the unpaid invoice.
The bet: Claude is now reliable enough at tone-grounded writing — not just grammatical writing — that the chase-email step doesn't need a human. Tally reads your books, watches every invoice, and the moment a payment slips, drafts a follow-up calibrated to the client (their payment history, their tone, how late this one is) in the freelancer's own voice. The freelancer approves and sends — or auto-sends, depending on the stage. The 4.5-hour-per-week collection-tax becomes 5 minutes.
What It Does
A freelancer or small-shop owner OAuth-connects QuickBooks, Xero, and/or Stripe in three clicks. Tally then watches every issued invoice. The moment one slips past its terms, Claude drafts a follow-up in the freelancer's voice — calibrated to the client and the stage. The freelancer approves it from the inbox, or sets the channel to auto-send for low-stakes reminders.
- Read-only sync — connects to QuickBooks Online, Xero, and Stripe Connect via OAuth. Pulls invoices + customers into the Tally workspace. Never writes back. Tally never marks invoices paid, never changes a client record, never files anything. The trust posture is "we read your ledger, we don't touch it."
- Overdue detection — automatic status tracking:
open → overdue → chased → paid. Tally flags invoices the moment they slip past the customer's terms. - Calibrated reminders — Claude drafts each follow-up grounded in the freelancer's voice profile, the specific client's payment behavior (pays-fast / on-time / slow / ghosts), and the days-overdue stage. Stage 1 is soft. Stage 3 is firm but never aggressive. Never sounds like a debt collector.
- Auto-escalation per stage — tone escalates calibrated to stage: gentle reminder at day +3, follow-up at day +14, firm note at day +30. Tally never crosses the legal line into "collection" language — explicitly grounded in FDCPA compliance from the system prompt up.
- Stays polite — Tally never reconciles, never files, never changes ledger entries on its own. The freelancer is always the sender. Tally just drafts.
- Stripe subscriptions — $49/mo Freelancer or $199/mo SMB, managed through Stripe Billing.
Beyond the single follow-up, Tally gives you:
- A client library with per-client payment behavior (
pays_fast / on_time / slow / ghosts), average days-to-pay, total outstanding, and tone profile — so reps know who to chase first and how - Invoice inbox with every invoice across every connected source, filterable by status, age, client, and amount
- Reminder timeline per invoice showing every draft, every send, and the actual payment-after rate per drafted reminder
- Voice profile + workflow rules the freelancer sets once: "always start with their first name, never use 'hi there', sign off with 'Talk soon' — except for legal clients then sign 'Best'." Claude honors these every time.
- Multi-source dedup — invoice that exists in both QuickBooks and Stripe (because the freelancer uses Stripe Invoicing → QB sync) appears once with both source flags. No double-chasing.
- Per-client tone notes — manual override per client if a particular CFO needs extra-formal language
The promise: 22% of invoiced revenue past-due becomes 4%, and 4.5 hours per week of collection emails becomes 5 minutes of approving drafts.
How I Built It
Architecture
Customer connects: QuickBooks / Xero / Stripe Connect / HubSpot / Gmail
│ read-only OAuth, multi-tenant
▼
/api/integrations/oauth/[provider]/start + /callback
│ per-tenant tokens in tally_integrations.config (JSONB, encrypted-at-rest)
▼
/api/sync/run → per-provider sync workers
│ pulls clients + invoices, recomputes status + outstanding
▼
Claude Sonnet 4.6 (per drafted reminder)
- client context: payment behavior, prior touches, tone notes
- voice profile: freelancer's signature phrases + sign-off
- drafts follow-up matched to client + days overdue + stage
│
▼
Amazon Aurora Postgres (shared cluster `hackathon-aurora-pg`)
├── tally_users — voice profile + workflow rules per user
├── tally_clients — client profiles + payment behavior + tone
├── tally_invoices — synced invoices + status + outstanding
├── tally_reminders — drafted + sent follow-ups + stage tracking
└── tally_integrations — per-tenant OAuth tokens (config JSONB)
│
▼
Gmail (OAuth-sent from your address) OR Resend → polite email to client
The architectural bet is multi-tenant OAuth across 4 incompatible accounting APIs. QuickBooks Online uses Intuit's OAuth 2.0 with a realmId per company. Xero uses OAuth 2.0 with PKCE + a tenant-id flow you have to fetch from /connections before any data call. Stripe uses Stripe Connect Standard with a stripe_user_id returned from the token exchange. HubSpot uses OAuth 2.0 Public App with a hub_id. Four different OAuth shapes, four different ways to identify "which sub-tenant inside this customer's account is the one we synced."
The solution is the tally_integrations.config JSONB column. Whatever shape each provider returns — realmId, tenant-id, stripe_user_id, hub_id — gets stored alongside the access token and refresh token. The getFreshAccessToken() helper auto-refreshes against each provider's token endpoint with the right grant shape. Downstream code asks for "the access token for quickbooks" and gets back a fresh one, no matter how stale the stored token was.
Tone calibration is where the voice profile earns its keep. The system prompt for the draft step pulls in the freelancer's voice_profile (signature phrases, sign-off, salutation rules), the client's payment_behavior and tone_profile, the invoice's days_overdue and stage, and the drafted-reminder history for this invoice. Claude generates a single draft — stage-appropriate, voice-mimicking, never debt-collector-toned. The drafted reminder is FDCPA-grounded at the system prompt level: no threats, no "this is your final notice," no debt-collector language. Just polite, voice-mimicking, human-feeling chase emails.
Tech Stack
| Layer | Technology |
|---|---|
| AI Model | Anthropic Claude Sonnet 4.6 |
| Accounting Integrations | QuickBooks Online OAuth, Xero OAuth + PKCE, Stripe Connect Standard, HubSpot OAuth, Gmail OAuth |
| Database | Amazon Aurora Postgres (tally_* tables) |
| Backend | Next.js 16 API Routes + Vercel Cron |
| Auth | Supabase Auth + Google OAuth |
| Payments | Stripe (subscriptions + webhooks) |
| Resend (templata.org) + Gmail OAuth | |
| Frontend | Next.js 16, React 19, Tailwind CSS, shadcn/ui |
| Deployment | Vercel (Fluid Compute, us-east-1) |
Challenges
- Five OAuth flows, five tenant-id shapes — QuickBooks (
realmId), Xero (tenant-id from/connections), Stripe Connect (stripe_user_id), HubSpot (hub_id), Gmail (no tenant — just user-email). Building one unified OAuth lib that handles all five without per-flow forks took two iterations. The fix: a per-provider config map insrc/lib/oauth/providers.tswith provider-specific scope + extraParams, and a single shared callback route that dispatches token-shape parsing based on the provider id. One lib, five OAuth flows, zero downstream branching. - The "never sounds like a debt collector" guarantee — Claude's default chase-email tone drifts into debt-collector land fast: "this is your final notice", "your account is delinquent", "we may take further action." All of those phrases are legally dangerous if Tally isn't a licensed collector (it isn't). The fix was an explicit FDCPA-grounded system prompt + a
do_not_sayallow-list in the voice profile that includes those phrases. Combined with stage-aware tone calibration, draft language never crosses the line. - Multi-source invoice dedup — a freelancer using Stripe Invoicing that syncs to QuickBooks ends up with the same invoice in both Tally syncs. Dedup logic keys on
external_id+ amount + due_date and merges sources. The same invoice appears once withexternal_source: ["quickbooks", "stripe"]and a unified status. - Per-tenant token refresh that doesn't thrash the API — every provider has a different access-token lifetime (Intuit: 60 min; Xero: 30 min; Stripe Connect: indefinite; HubSpot: 30 min; Gmail: 60 min). Refreshing on every API call would burn the rate limit. The fix is the
isExpiring()predicate that checksexpires_at - 60s < now()and only refreshes if within the window — otherwise reuses the cached token. Downstream code callsgetFreshAccessToken()and never thinks about refresh.
What's Next
- Per-client payment-time prediction — Claude predicts "when will this client actually pay" based on their history, so reminders go out at the right moment
- Reply detection — when the client replies "sending tomorrow", Tally pauses the cadence automatically and doesn't double-chase
- Spanish-language drafts — for freelancers with bilingual client bases, draft in the client's language automatically
- Late-fee suggestion — flag invoices where the contract permits a late fee and offer to add it to the next draft (never silently)
- Cash-flow forecast — given the invoices in outstanding state, predict the next 30/60/90-day cash receipts
- Multi-currency support — agencies billing international clients get currency-aware drafts + thresholds
Sample Workflow
Tally ships with a seeded demo workspace (Brookline Studio, a small design agency) with 18 sample invoices across 6 clients, 3 of which are overdue. Judges can sign in, click Sync now on the integrations dashboard to populate the workspace, then navigate to /dashboard/invoices to see the inbox with overdue-status flags, and to /dashboard/reminders to see the drafted follow-ups with voice mimicry working from the seeded voice_profile. The OAuth flows for QuickBooks, Xero, Stripe Connect, HubSpot, and Gmail are all fully wired against the production OAuth apps — any judge with a QBO sandbox or Xero developer org can connect their own data and see the real read-only sync run.
Team
Built solo by @kyisaiah47 for the H0 Hackathon.
Built With
- amazon-web-services
- anthropic
- aurora-postgres
- claude
- gmail
- hubspot
- nextjs
- postgresql
- quickbooks
- react
- resend
- shadcn-ui
- stripe
- stripe-connect
- supabase
- tailwindcss
- typescript
- vercel
- xero

Log in or sign up for Devpost to join the conversation.