ยท8 min read

Copia Cash

A look back at the journey of Copia Cash, the personal finance app

TL;DR

Copia Cash started as a throwaway budgeting tool for my wife and me and grew into a real SaaS product. This post walks through the early Tiller + Google Sheets stack, the move to Plaid and Clerk, our provider-agnostic transaction model, and building it with my partner Dillon Streator.

What is Copia Cash?

Copia Cash is a personal finance app that helps you track your money and budget your finances.

How it started

I originally built Copia as a quick throwaway project to help my wife and I reach our financial goals.

I used to use Mint back in the day and when it shut down I was looking for a new solution and found that most of what was out there just was not cutting it.

They were either too expensive or too complex for what I was looking for. I wanted to make something simple and easy for us to use.

It originally started as just a single page, nothing crazy, it had all of our transactions and one chart. I built it pretty quick, only a few weeks, then just left it for a few months before coming back to it for some much needed improvements.

Over a span of a few months of development on Copia, I was starting to see a vision for Copia that was bigger than just a throwaway project. I thought to myself:

"If I could build something that helps us, maybe it could help others too."

Initial Architecture

I'm going to take us back to my initial thought process of how I wanted to approach building Copia.

import { TimeMachine } from 'time-machine'
import { DateTime } from 'luxon'
 
const timeMachine = new TimeMachine()
 
const oneHalfYearsAgo = DateTime.now().minus({ years: 1.5 });
const presentDay = DateTime.now();
 
// going back
await timeMachine.Rewind(oneHalfYearsAgo);

Alright, I'm going to go with a MERN stack. Mainly because it's popular and I've never built something in this stack before. Here is the stack:

  • React - Frontend framework
  • Express - Backend framework
  • MongoDB - Database
  • Nx - Monorepo manager

Should I use Go? It is my favorite language...

No, let's keep it all the same language. It probably will just be a throwaway project anyways.

TypeScript of course. Why? Types, that's why.

Okay let's talk about the architecture. First the data. Where would the data come from?

  1. I could manually enter in the data.

This could be a simple way to get started, but it would be super painful and annoying to have to enter all of our transactions in manually.

  1. I could export it from the bank and dump it into an Excel file and read that.

Keep adding files in a directory?

Keep adding to the same file?

Both of these options would be painful and annoying for me.

  1. I could use Plaid.

This would be a lot of work and overhead for something that might be a throwaway project.

I would have to handle the post processing of the data because Plaid gives it to you raw.

  1. What about Tiller?

Tiller is a tool that allows you to connect to your bank and it will sync the data to a Google Sheet.

This is close to the Plaid route, since we are using a 3rd party service to connect to our bank, but Tiller does a lot of the post processing so it won't be as heavy as Plaid.

This beats having to do any manual work...

Tiller sounds like a good medium between manual and Plaid.

So let's go with Tiller.

If we use Tiller we can connect to our bank, dump the data into a Google Sheet then use the Google Sheets API to read that data and sync it to our database.

// going to the present day
await timeMachine.setDate(presentDay);

This was the initial thinking for the architecture of Copia Cash.

So this is exactly what I did:

  • Scaffold the project with Nx
  • Set up 2 apps, one React and one Express
  • Set up a local mongo database with docker compose
  • Signed up for Tiller, connected my bank and dumped the data into a Google Sheet.
  • Wrote up a simple endpoint to "sync" the google sheets data into mongo.

This code is long gone by the way. Lost in the thousands of commits that have been merged into Copia Cash.

It was something along the lines:

  • Find columns that have a letter.
  • Batch processing of the rows.

The biggest pain of all of this was working with the Tiller Chrome extension and setting up what they called "Auto Sync", but man did it have a lot of problems.

first screenshots of the app

When it started to get real

Over a span of 6 months or so, I was on and off working on Copia. I started to add more features like recurring transactions, budget, tons of analytics and more.

I even had a few AI features like summary generation using local models from Ollama. Once I was here, I decided I might want to put it out in the world and see if anyone else might get value from it.

// going back
await timeMachine.Rewind(oneHalfYearsAgo);

Okay, if we eventually want users let's think about what we have. Is it realistic to have everyone have to buy Tiller and have their data in a Google Sheet? Probably not.

I feel like Tiller is for people who don't want to pay for a SaaS product, so it seems almost like an anti-pattern to have everyone have to own Tiller.

Manual entry? Maybe, yes at some point, but for now we need syncing.

We also need an auth layer. Do we build our own? Probably not since it looks like we can use Auth0 and Clerk for free until we have a large number of users.

// going to the present day
await timeMachine.setDate(presentDay);

Authentication

Auth0 was the original choice for authentication in Copia but later we switched to use Clerk instead. Luckily this was before we had any user or even had the application in the cloud, so we didn't have to worry about any type of migration.

Plaid Syncing

Once I got Plaid fully set up, it was time to really study the API and the domain entirely. One of the biggest things I learned from developing Copia is that you really need to understand the domain you are working in, in order to build a solid data model that can be scaled and maintained. Obviously it's not possible to get it 100% right away but it will save you headache later on.

Copia Cash, as it is today, has a very complex syncing architecture, but at the time, it was very simple. Call plaidClient.transactionSync() and parse the response.

const response = await plaidClient.transactionsSync(
  transactionSyncRequest
);
 
const transactions = createTransactionRecords(response.transactions)
 
db.insert(transactions);

Now it's something along the lines of:

Listen for plaid webhook -> enqueue job to process sync -> process sync -> kick off receipt transaction link event

It's not all here but there is a ton more here like syncing connection, applying user rules, and more.

That one feature (provider agnostic)

One of the most important features of Copia is that it is actually fully provider agnostic. Which from our research, currently does not exist in any other personal finance app.

We are able to do this by creating canonical ids for each transaction.

  for (const transaction of transactions) {
    const canonicalTransactionId =
      canonicalIdByProviderTransactionId[transaction.transaction_id];
 
      ... more code ...

Meaning, if you sync with Plaid initially, you can disconnect from Plaid and connect with another data provider, like Finicity, and have 0 problems syncing your data.

The data flows into Copia the same way regardless of the provider

I've said too much.

Partnership

I am happy to say that I have partnered with an ex-colleague and a good friend of mine on Copia for some time now.

Dillon Streator is one of the most talented developers I've ever had the pleasure to work with and he has made an incredible impact on the project, in areas like:

  • Architecture
  • Product decisions
  • Code quality
  • Documentation
  • Testing
  • UI development
  • Large features
  • ...the list goes on and on

He and I are continuously collaborating on Copia and also maintaining some of our open source products in the osbytes organization.

Where are we today?

Today Copia Cash has users. Probably not as many as we would like to have but we are adding features based on feedback from the users we do have.

We've opened up a free tier in Copia and have plans to release an open source version as well.

Final Thoughts

Copia Cash is going strong in its development. I was actually working on the app concurrently while writing this blog post.

We have some cool plans for Copia and hopefully it will grow, gain traction and become a successful product.