Offline-First Landscape
When we set out to build Marco, we knew we were committing to two very difficult requirements:
IMAP-based, not API-based
Cross-platform: web, macOS, iOS, possibly more
We had a handful of additional ancillary requirements. One of these was offline-first, and I can now say confidently that we drastically underestimated its complexity.
I mentioned in a previous blog post that Missive was my daily driver in the recent past. It lacks offline support though, and this is one of the major downfalls of the product.
At Marco we believe that full offline support is crucial. "Managing your emails on an airplane with no wifi" is an example use case we frequently come back to. You should be able to read, delete, respond, and organise your emails with no internet connection. When you land and connect to wifi, everything should seamlessly sync.
That said, Marco is not a simple todo app. Marco is not an application that starts with zero data and grows in size gradually, as is the case with user-generated content like Notion, etc.
Marco is an application that deals with hundreds of MB of data, and hundreds of thousands (or millions) of rows/entities.
Essentially this means we are instantly jumping into the top 1% of heavy-duty use cases for offline-first implementations. Over time we realised that this actually rules out almost all available offline-first options.
Starting Point: WatermelonDB
I spent about a week deeply investigating the offline-first options available to us, in August 2025.
We (perhaps naively) had committed to the idea that our offline-first architecture should be database-agnostic: the offline-first logic should "end" at the API layer. We did not want to manage sync tables or schemas in Postgres, we wanted to write API endpoints, and manage our database ourselves.
Here's a rundown of the initial offline-first options we looked at:
WatermelonDB: FOSS, self-hosted, database agnostic. Been around for ages, used in many production applications.
PowerSync: Not quite FOSS, but has a free Self-Hosted Edition. Requires Postgres-level integration. Very complex architecture, requires both changes to Postgres and a separate HA MongoDB deployment cluster. Appears to have been around quite awhile, but all their case studies are on demo/tiny/side projects.
ElectricSQL: Looked interesting, but was in the middle of a complete rewrite. Requires Postgres-level integration. New version only handles data sync one way: it does not handle mutations.
There are many other options, including RxDB, MongoDB Atlas, Couchbase, and on and on. The three listed above are the options that we deeply investigated. As will become clear, we should have looked further at this stage.
We settled on WatermelonDB and built the initial alpha version of Marco on it. The backend implementation is rather simple: there is a "pull" endpoint to GET data, and a "push" endpoint to POST mutations.
It is important here to note that although Marco is a native application in some targets, it also must run in a web browser. While we may have access to a filesystem or "true" SQLite in native targets, our common denominator is web, where persistent storage options are very limited.
On the (web) frontend, Watermelon uses IndexedDB (as do essentially all other options, even the WASM SQLite options are usually SQLite-on-top-of-IndexedDB). However, it turns out Watermelon faces a serious problem that all other relational frontend databases face: IndexedDB performance is terrible. To solve this, Watermelon uses a LokiJS adapter, which is literally just an in-memory database.
Yes, you heard that right. To get around IndexedDB performance issues, Watermelon uses LokiJS to hold the entire database in memory. When your database size is 100MB+, this starts to become a serious problem.