All projects

Case study · Client project

One platform for hiring contractors, from job post to paid invoice.

Naoi brings a Dublin hiring company's whole contractor process into one place. I was the only developer on it.

NAOIRailsJavaScript

every branch is a feature, tap one

Client
Naoi, Dublin
Users
Hiring managers and contractors
My role
Sole developer, end to end
Team
Me and a project manager
Stack
Rails, JavaScript, Action Cable, PDFKit
Home

Screens are from the Naoi Figma design, shown with sample data.

Hiring one contractor meant working across several separate platforms. Recruiters ran interviews in one tool, emailed candidates from another, printed invoices by hand and signed contracts somewhere else.

Nothing connected the job, the interview, the contract and the hours worked, so every step started by copying details over from the last one.

interviews in one toolemails in anotherinvoices printed by handcontracts signed elsewhere
all of it in Naoi

Each step hands its data to the next one, so nobody copies anything twice.

  1. 01

    Post a job

    The company admin lists the description, skills needed, rate, country and work setup.

  2. 02

    Match contractors

    Contractors the company has added show up on the job when they fit. Skills count most, then rate, then location, then availability.

  3. 03

    Interview

    A contractor's availability is converted to the client's time zone, with a note when their hours don't line up with the job.

  4. 04

    Contract

    When a contract ends, the hiring manager gets an email and can send a new one. The contractor keeps their old contracts and past rates.

  5. 05

    Timesheet

    The contractor submits hours. The client approves or disputes them.

    How it works
  6. 06

    Invoice

    An approved timesheet becomes a PDF invoice, and the client is asked to pay.

    How it works
Post a job
Match
Shortlist

The hardest problem was multi-tenancy. Every company works in its own space inside one app and one database.

Management asked for the separation to live in the application code, by scoping every query, not at the database level. We also chose not to use a multi-tenancy gem, so the scoping was ours to build and ours to get right.

ruby
# Finds a job from any company. One missed scope like this is a leak.Job.find(params[:id])# Finds a job only inside the signed in user's company.current_company.jobs.find(params[:id])

Early builds leaked records to users who shouldn't see them. Scope level separation only works if every query, page and email remembers its scope, and one forgotten scope is enough.

Every new feature started with two questions: which company owns this data, and who may see it?

I kept a running list of what needed protecting and checked each new query, page and email against it. It made every feature slower to ship, but it held the data together.

An approved timesheet turns into an invoice on its own, so nobody retypes hours, and no invoice goes out for hours the client hasn't approved.

StatusHow it gets thereWhat happens next
SubmittedThe contractor sends their hoursThe client reviews it and can leave comments
DisputedThe client marks it disputedThe contractor gets an email that it needs fixing
ApprovedThe client approves itThe client is emailed to pay, and the invoice is generated
OverdueThe deadline passes with nothing sentAn email goes out that it's overdue

The invoice is a PDF built with PDFKit from the approved timesheet. It shows who it's from and who it's for, the job and its description, the rate, the hours worked and the total.

Every job has its own real-time chat between the client and the contractor, built with Action Cable.

During hiring they use it for interview changes and documents. After the hire, the same thread carries on for timesheet questions, so the context from the interview is still right there.

With 300+ people chatting at once, the chat slowed down. Loading a conversation ran an extra database query for each message, the classic N+1 problem. I added caching and went back through the chat's queries to remove the extra ones.

Job chat

Naoi replaced a stack of separate tools with one platform. The numbers are our estimates.

1platform instead of several tools
40+invoices generated a month
300+people chatting at once
  • Faster. Approving a timesheet produces the invoice right away, with no printing or retyping.
  • Cheaper. One platform instead of paying for several tools.
  • Safer. Each company's data stays inside its own space.
  • Built to grow. Multi-tenant from day one, so new companies and countries can join without a rebuild.

The first release shipped with its share of bugs. Fixing them against real use shaped the version running today.

01

Security by habit needs a system

Scope level separation only holds when the checks are written down and repeated for every feature, not kept in someone's head.

02

Own the whole product

As the only developer next to a non-technical project manager, I turned client requests into the data model, the screens and the emails myself.

03

Automate the handoffs

Most of the old back and forth was people chasing each other. Statuses and emails now do the chasing.

That's the story of Naoi

See the other projects