FreelensFlow
All-in-one freelance business management platform. Automate your workflow with time tracking, invoicing, lead management, cold outreach automation, and productivity analytics — everything you need to scale your freelance business in one place.

Overview
FreelensFlow is a comprehensive SaaS platform designed to solve the fragmentation problem in the freelance economy. Instead of juggling Trello for tasks, Excel for finances, and Gmail for outreach, freelancers can manage their entire business lifecycle in one dashboard. The platform integrates lead generation scrubbing, automated cold email sequences, invoice generation with Stripe integration, and project management tools. It uses a modern tech stack to ensure speed, reliability, and scalability.
Key Features
- Automated Cold Outreach: Scrape leads and send personalized email sequences.
- Financial Dashboard: Track revenue, expenses, and generate professional invoices.
- Project Management: Kanban boards and time tracking tailored for client work.
- Client Portal: Allow clients to view project status and pay invoices directly.
How it was built
The problem
A working freelancer runs their business across half a dozen tools that do not know about each other: a spreadsheet for invoices, a Kanban board for client work, a personal inbox for prospecting, and a stopwatch app for billable hours. The same client ends up existing four times under four slightly different spellings, and none of the four copies agree. That fragmentation is not just annoying — it is where the money leaks. Hours logged in one place never make it onto an invoice. A lead that went cold in the inbox is never connected to the proposal that was written for it. Revenue reporting becomes an end-of-quarter archaeology exercise rather than something you can look at on a Tuesday. FreelensFlow started from the observation that lead, outreach, proposal, project, time entry, invoice and payment are not seven products. They are one record moving through seven states, and almost all of the useful value lives in the joins between those states rather than in any individual feature.
Approach & architecture
The architecture follows from that premise: one Postgres schema in Supabase holds the whole lifecycle, and every module is a different view over the same spine rather than its own island of data. A lead is promoted into a client in place — the row keeps its identity and gains fields — so outreach history stays attached after the deal closes instead of being orphaned in a separate CRM table. Time entries reference project tasks; invoice line items are a projection over those entries rather than a re-typed copy of them, which is what makes 'which hours did I actually bill for?' a query instead of a reconciliation.
Authorization lives in the database rather than in the application layer. Postgres row-level security policies are the single gate: the API surface can be thin because a leaked or mis-scoped query still cannot read another account's rows. That decision is what makes the client portal cheap to build — it is not a second application, it is the same tables behind a narrower policy, so a client sees their own projects and invoices through the same code paths the freelancer uses.
Next.js App Router splits the work along the obvious seam. Dashboard reads are Server Components that query Supabase directly and stream, so the heavy financial and analytics views never ship their data-fetching logic to the browser. Client Components are reserved for the genuinely interactive surfaces — dragging Kanban cards, running a timer, building an invoice. Anything that must not run on the client or must outlive a request — sending an outreach sequence, generating an invoice PDF, reconciling a Stripe webhook — runs in a Supabase Edge Function with the service role, keeping privileged credentials off both the browser and the rendering layer. TypeScript types are generated from the database schema, so a column rename surfaces as a compile error rather than as an undefined at runtime.
Engineering challenges
Cold outreach that stays deliverable
Sending sequenced email from an application is mostly a deliverability and correctness problem, not a sending problem. A sequence is modelled as scheduled steps with a per-recipient state machine, so a reply, a bounce, or a manual takeover halts the remaining steps for that contact rather than mailing a prospect who already answered. Sends are dispatched from scheduled Edge Functions with an idempotency key per step so a retry after a network failure cannot double-send — the most damaging bug this feature could have. Volume is throttled and spread rather than blasted, authentication records are configured on a dedicated sending subdomain so campaign mail cannot poison the freelancer's primary domain reputation, and unsubscribe handling is enforced at the data layer so a suppressed address cannot be re-enrolled by a later import.
Timers that survive a closed laptop
A naive timer is a counter in React state, which dies with the tab and lies after a sleep or a timezone change. Instead the client records immutable start and stop events with server-authoritative timestamps, and elapsed time is derived rather than stored. That makes a crashed browser recoverable, makes concurrent timers on two devices detectable instead of silently corrupting, and — most importantly — makes every billed hour reproducible from an append-only log. Corrections are new adjusting entries with a reason, never a destructive edit, because an invoice that has already been sent to a client must always be explainable from the underlying data.
Invoices that must never drift
Rates change, projects get re-scoped, and clients rename themselves. If an invoice renders live from current data, last quarter's paperwork silently rewrites itself. Issued invoices therefore snapshot their line items, rates, tax treatment and billing details at the moment of issue, and become immutable; changes are handled as credit notes or new invoices. Invoice status is a state machine — draft, issued, paid, void — rather than a set of independent booleans that can contradict each other.
Stripe as a second source of truth
Payment state genuinely lives at Stripe, not in the application database, so the two have to be reconciled rather than assumed to agree. Webhooks are verified by signature, processed in an Edge Function, and recorded by event id so replays and out-of-order delivery are idempotent. The application treats Stripe as authoritative for whether money moved and treats its own rows as a cache of that fact, which avoids the classic failure mode where a checkout succeeds, the webhook is missed, and the client is left staring at an invoice marked unpaid.
Stack decisions
- FrameworkNext.js (App Router) with TypeScript
- Server Components let data-heavy dashboard views query the database directly and stream, keeping query logic and credentials off the client while still allowing rich interactivity exactly where it is needed.
- Database & authSupabase Postgres with row-level security
- The lifecycle is inherently relational — leads join to projects join to time entries join to invoices — so a real SQL database is the right shape. Putting tenant isolation in RLS makes the strongest security boundary the hardest one to bypass.
- Privileged & background workSupabase Edge Functions
- Outreach dispatch, PDF generation and webhook handling need service-role access and must run outside the user's request. Keeping them in the same platform as the database avoids a second deployment target and a second set of secrets.
- PaymentsStripe
- Card data, SCA flows and dunning are liabilities worth outsourcing entirely. Hosted checkout plus signed webhooks keeps the application out of PCI scope while still giving a reliable, auditable payment event stream.
- Client portalSame schema, narrower RLS policy
- Building a separate client-facing app would mean duplicating models and drifting apart over time. Scoping access with policies means the portal is correct by construction and gets every improvement to the main app for free.