← MellowKraft
Visit Portly open_in_new
Case Study SaaS · Web App Live Product

Building the operating
system for India's
freelancers.

Portly started as a question: why do the best freelancers still chase payments over WhatsApp? We set out to build the tool we wished existed — and ended up shipping a full operations platform.

13
Data models
8
API route groups
5
Email automations
Live
portly.online
portly.online/dashboard
Portly Operations Dashboard
The Problem

India has 15 million freelancers.
Most of them run their businesses on WhatsApp and spreadsheets.

receipt_long

Invoice chaos

PDF invoices emailed manually, tracked in spreadsheets, chased over WhatsApp. Payment status unknown until money arrives.

hub

Fragmented tools

One app for invoicing. Another for project tracking. A third for time tracking. None of them talk to each other.

account_balance

TDS blindspots

Section 194J compliance — 10% TDS above ₹50k per client per fiscal year — is a tax landmine most freelancers discover too late.

"The best freelancers are excellent at their craft. But they're spending 20–30% of their week on operational overhead that has nothing to do with it."

— The strategic audit that started Portly

The Strategic Insight

Most invoicing tools are built for the freelancer. We built Portly for the client experience first.

Most tools optimize for the person sending the invoice. Portly's core insight was different: if the client experience is frictionless, the freelancer gets paid faster, looks more professional, and builds stronger relationships.

This led to the product's defining feature: a zero-login client portal. No account creation. No password. Clients click a link, and immediately see project progress, milestone status, deliverables, and an invoice they can act on — all without touching a signup form.

That one decision shaped the entire architecture. It forced us to think carefully about security, consent, and trust — and the answers produced something genuinely different from every other tool in this space.

The zero-login decision

Clients access their project portal via a unique shareToken embedded in a URL. No account. No password. The token is stored in Firestore, validated server-side, and scoped to a single project. Access can be revoked instantly by the freelancer.

check_circle UX win

Clients see work immediately. No signup friction = no abandoned links.

security Security trade-off

Relies on token obscurity + HTTPS. Acceptable for short-term client collaboration. Token revocation gives freelancer full control.

What Was Built

Not an invoicing app. An operations platform.

Portly covers every layer of a freelancer's business — from first lead to paid invoice, with everything in between.

dashboard

Operations Dashboard

The command centre. Every number that matters, live.

KPI Grid

Total outstanding, cash received, overdue amount, lead-to-booking rate, on-time payment rate, repeat client count — all live.

Lifecycle Funnel

Visualises the full pipeline: Lead recorded → Qualified → Invoice sent → Paid. Shows conversion at every stage.

Client Health

Per-client payment reliability score, last contacted date, and suggested next action — all surfaced automatically.

receipt

Invoice Engine

Built for India's payment reality.

  • check_circle GST toggle (18%) with auto-calculation
  • check_circle Payment milestones (30/40/30 default, fully customisable)
  • check_circle UPI QR code generated dynamically in invoice email
  • check_circle TDS tracker (Section 194J, ₹50k threshold, Apr–Mar FY)
  • check_circle Status audit trail: draft → sent → viewed → partial → paid
link

Zero-Login Client Portal

The feature that makes everything else matter.

  • check_circle Project progress, tasks, deliverables — real-time
  • check_circle Invoice with payment options embedded
  • check_circle Consent-gated comments (GDPR-style privacy flag)
  • check_circle Freelancer moderates comments before publish
  • check_circle QR code + shareable link + instant revocation
funnel

Lead Pipeline

Kanban-style sales tracking, built for one-person teams.

  • check_circle Kanban: New → Contacted → Proposal → Negotiating → Won/Lost
  • check_circle Lead score (0–100), source tracking, stage history
  • check_circle One-click convert lead → client → project
schedule

Time Tracker + CRM

Billable hours and client relationships, in the same place.

  • check_circle Live timer with billable/non-billable flag
  • check_circle Cost rate vs. bill rate per entry
  • check_circle Client interaction log (meetings, calls, proposals)
  • check_circle Follow-up reminders with recurrence (daily/weekly)
Under the Hood

The decisions that made this product possible — and fast.

Architecture is strategy made concrete. Here are the choices that mattered.

01

Firestore REST API — not the SDK — inside Cloudflare Workers

Cloudflare Workers have a 10MB bundle size limit. The Firestore SDK alone is ~2MB. Calling Firestore via its REST API keeps the Worker lean, gives full control over error handling and retries, and eliminates SDK overhead. The trade-off: manual JWT generation and OAuth2 token exchange. Worth it.

Service account → sign JWT → exchange for OAuth2 token → call Firestore REST API → convert response ↔ plain JS objects
02

Magic link auth — no passwords stored

When a user signs up, there's no password. An email with a 1-hour expiring link is sent. The token is SHA-256 hashed before storage — what's in Firestore is the hash, never the raw token. Single-use: verified and deleted in one transaction. Email receipt proves identity. No password database = no password breach.

nanoid(32) token → SHA-256 hash → store hash + email + 1h TTL → email raw token → user clicks → hash received token → match → Firebase custom token → login → delete hash
03

Status history as an append-only audit trail

Every state change — invoice status, lead stage — is appended to a statusHistory array instead of overwriting. This gives a full audit trail: when did the invoice move from sent to viewed? Which lead sat in negotiating for 3 weeks? It's event sourcing without a separate event store.

statusHistory: [
  {"{ from: 'draft', to: 'sent', changedAt: '…', source: 'manual' }"}
  {"{ from: 'sent', to: 'viewed', changedAt: '…', source: 'webhook' }"}
  {"{ from: 'viewed', to: 'paid', changedAt: '…', source: 'manual' }"}
]
04

Optimistic UI with rollback — feels instant, fails gracefully

Every user action (toggle a task, update status) updates the UI immediately before the API call completes. If the API fails, the previous state is restored and an error is shown. On Indian mobile connections with variable latency, this makes the app feel native-speed regardless of network conditions.

05

Idempotent operations — network timeouts can't create duplicate invoices

Every create endpoint accepts an optional idempotencyKey. If the same key is sent twice (e.g., user retried after a timeout), the existing resource is returned instead of creating a duplicate. No double invoices. No duplicate projects. The client-side generates the key; the server enforces uniqueness.

The full stack

React
React
Frontend
Cloudflare
Cloudflare
Workers + Pages
Firebase
Firebase
Auth + Firestore
Tailwind
Tailwind CSS
Styling
Vite
Vite + PWA
Build + installable
TypeScript
TypeScript
Type safety
R
Resend
Email delivery
QR
QR Code
UPI payments
Email Automations

5 automated emails. Each one earns its keep.

magic_button
Magic Link — sign-in without a password
Triggered on signup. SHA-256 hashed token, 1-hour TTL, single-use. The email proves identity; the hash in Firestore proves nothing was stored in plaintext.
receipt
Invoice Sent — to the client, on freelancer action
Fully branded email with itemised line items, GST, dynamic UPI QR code with amount pre-filled, bank transfer details, PayPal link, and a portal link. Everything the client needs to pay in one tap.
notifications
Invoice Reminder — on overdue follow-up
Freelancer manually triggers. Friendly tone, emphasises due date, same payment options. Tracks last reminder timestamp to prevent accidental duplicate sends.
update
Project Update — freelancer notifies client
Free-text update with project title and portal link. Keeps clients informed without requiring them to log in or check a separate dashboard.
schedule
Reminder Notifications — Cloudflare Cron, daily
A Cloudflare scheduled Worker runs daily, checks all pending reminders within a 24-hour window, and flags them for notification. Email dispatch in progress — the infrastructure is built, the sending logic is being wired.
The Result

A live, production SaaS product — built the same way we'd build yours.

Portly is our own product, built using the same process we use for every client: strategy audit first, architecture decisions second, code last. It's not a demo. It's live at portly.online.

A MellowKraft production

← Back to MellowKraft