Dueflo
AI-powered accounts receivable automation SaaS for US small businesses — connects to QuickBooks Online via OAuth, syncs overdue invoices, and automatically sends AI-written collection emails in a 6-step escalating sequence over 45 days. Built with Next.js 15, tRPC, Drizzle ORM, Supabase, Stripe, and the Claude API.
Full Tech Stack
Overview
Dueflo is a production SaaS that solves one of the most painful problems for small business owners — chasing overdue invoices. It connects to a customer's QuickBooks Online account via OAuth 2.0, identifies every overdue invoice, and launches a fully automated 6-step email collection sequence powered by Claude AI. Each email is individually generated based on the invoice amount, days overdue, debtor payment history, and prior communication engagement. The sequence escalates in tone from friendly to firm over 45 days. When a debtor pays — detected by polling the QBO API for a zero balance — the sequence stops immediately and the invoice is marked recovered. The platform is built as a single Next.js 15 application with tRPC for type-safe API calls, Drizzle ORM against a Supabase PostgreSQL database, Trigger.dev for background jobs, Resend for email delivery and tracking, and Stripe for subscription billing with plan-based feature gating.
Repositories
QuickBooks OAuth 2.0 Integration
Full OAuth 2.0 flow with CSRF protection, token encryption at rest (AES-256-GCM), and automatic token refresh. Syncs overdue invoices every 6 hours via a Trigger.dev scheduled task and on-demand via webhooks. RealmId deduplication prevents free-trial abuse across multiple accounts.
- OAuth state stored in a signed cookie and verified at callback to prevent CSRF attacks.
- Access and refresh tokens encrypted with AES-256-GCM before storage — never stored in plaintext.
- QBO webhook triggers an immediate sync on Invoice or Payment change events, reducing detection latency from 6 hours to under 30 seconds.
- RealmId uniqueness check across organizations prevents the same QBO company from connecting to multiple free-trial accounts.
- Sync staggered across 360 time slots to avoid hammering the QBO API when many customers sync simultaneously.
- Multiple QBO connections supported on Business plan — each connection syncs independently and contributes to the same invoice pool.
AI Email Generation (Claude API)
Each collection email is generated fresh by Claude using a structured prompt that incorporates the invoice amount, currency, days overdue, debtor name, line items, communication history (opened, clicked, replied), and the current sequence step. Six tone levels escalate from friendly to final over the sequence lifetime.
- Six distinct tone levels: friendly → professional → direct → firm → urgent → final — mapped to sequence steps 1 through 6.
- Communication history passed to the AI on every generation: prior emails sent, whether they were opened, clicked, or replied to — the AI adjusts accordingly.
- Token usage and cost tracked per generation and stored in the communications table for cost monitoring.
- Plain-text body generated by AI; converted to HTML with inline styles for email client compatibility — no external CSS dependencies.
- Pay Now button injected via {{PAY_BUTTON}} placeholder — only rendered when the invoice has a verified customer-facing payment URL.
6-Step Collection Sequence Orchestrator
A state machine that manages the full lifecycle of each invoice's collection effort. Sequences are created when an invoice becomes overdue, advance through steps on a schedule (day 3, 7, 14, 21, 30, 45), and are paused or completed based on payment, reply, unsubscribe, or manual action.
- Sequence schedule: step 1 fires 3 days after creation, subsequent steps at day 7, 14, 21, 30, and 45 — configurable at the sequence level.
- Optimal send time scheduling: emails target Tuesday–Thursday 10am to maximize open rates.
- Sequences are automatically completed when QBO reports a zero balance on the invoice.
- Paused with reason tracking: invoice_paid, debtor_replied, manual, excluded, unsubscribed — each maps to a different recovery action in the dashboard.
- Plan-based sequence limits enforced at start time: Starter 25, Professional 100, Business unlimited — prevents overuse without plan upgrade.
- Send Now override available from the dashboard — triggers the next sequence step immediately via a Trigger.dev task.
Payment Detection
Every sync cycle checks QBO for a zero balance on all active (overdue or in_sequence) invoices. On detection: invoice status → paid, active sequence → completed with reason invoice_paid, debtor behavior → slow, Slack notification fired if configured.
- Only active invoices are checked — O(active) not O(all), keeping sync fast as the invoice backlog grows.
- Payment link field refreshed on every upsert — internal QBO URLs (requiring QBO login) are filtered out and stored as null, preventing broken Pay Now buttons in emails.
- Debtor marked as slow payer on payment — future sequences for the same debtor start at a firmer tone.
- Slack webhook notification fired on payment if the organization is on the Business plan and has a webhook URL configured.
Custom Sending Domain
Professional and Business plan customers can verify their own domain with Resend and send collection emails from addresses like billing@theircompany.com. The full DNS verification flow is managed in-app.
- Resend domain API integration: create domain, retrieve DNS records, trigger verification, delete domain.
- DNS records table rendered in-app with per-record copy button — no need to visit the Resend dashboard.
- On verification: organizationSettings.sendingEmail updated to the custom address — all future emails use it automatically.
- Domain status tracked in the sending_domains table: pending → verified (or failed).
- Removal resets sendingEmail to the default shared Dueflo address.
Architecture
Single Next.js 15 App Router application — no separate backend service. Server Components, Route Handlers, and Server Actions coexist with the tRPC API layer.
tRPC v11 for all client–server communication: type-safe procedures with Zod input validation, protectedProcedure middleware that verifies Supabase session and loads organization context on every call.
Drizzle ORM against Supabase PostgreSQL — schema-first with full TypeScript inference. No raw SQL except for enum column filtering where Drizzle's type system requires it.
Supabase Auth handles user sessions (JWT cookies). On first login a database trigger creates an organization, user record, settings row, and organization member — the app never calls a separate signup API.
Trigger.dev runs two task types: a scheduled task (every 6 hours) syncing all active QBO connections, and on-demand tasks triggered by QBO webhooks (immediate sync) and dashboard actions (Send Now).
Resend handles all outbound email with open and click tracking via webhook. Collection emails are plain HTML generated from AI body text — no template engine.
Stripe webhooks drive subscription state: checkout.session.completed sets stripeSubscriptionId and trialEndsAt, customer.subscription.updated handles plan changes and cancellations.
QBO API interactions are read-only from the database perspective — Dueflo never creates or modifies invoices in QuickBooks, only reads balances and customer data.
Plan enforcement is layered: DB-level (plan limits checked before sequence creation), server-level (tRPC mutations check plan tier), and UI-level (locked fields and upsell prompts for non-qualifying plans).