Skip to content

Case study · Self-hosted invoicing and quotes

Mavri: Self-hosted invoicing and quotes

Free, open-source invoice and quote software you run on your own server: one Go container, one SQLite file, a REST API, signed webhooks and UBL and XRechnung export.

My role
Lead developer. I wrote the server, the interface, the API, the installer scripts and the documentation site.
Licence
AGPL-3.0
Links
Product siteDocumentationSource on GitHub
Mavri dashboard showing billed, paid, overdue and outstanding amounts

Context

Mavri is invoicing software that stays on your own server. A small business creates quotes and invoices, converts an accepted quote into an invoice, records payments and reads billed, paid, overdue and outstanding amounts from a dashboard, without opening a hosted billing account.

It is free, open source under AGPL-3.0, and ships as one Docker container with one SQLite data folder. Installers cover macOS, Linux and Windows through WSL2.

Problem

Hosted billing tools are easy to start but keep your records inside someone else's service. Self-hosted alternatives often ask for a web server, an API service, a database server and a queue before the first invoice exists.

Users

Freelancers and small businesses who want to own their invoicing data, and developers who want to connect a store or a script to billing through an API.

Operational workflow

Install with one command, create a business profile, add clients and catalog items, write a quote, convert it to an invoice, record the payment and download a backup. Stores can create invoices automatically through signed webhooks.

Engineering challenge

Billing history must not change when reusable records change, an integration must survive retried deliveries, and a rewrite must not strand the records people already have.

Architecture

One Go server serves the React interface and the versioned REST API from the same origin, with SQLite in a persistent data volume. There is no separate API host, queue or database server to operate. The reasoning, and what it does not solve, is in why Mavri serves the web app and API from one container.

Data model decisions

A document keeps a snapshot of the business, customer, line items, currency, tax choices and style used when it was made, so editing a client or a catalog price later does not rewrite an invoice already sent. Why an invoice should keep a snapshot explains the boundary and its cost.

Backend

The Go API exposes versioned REST endpoints, signed store webhooks and an MCP server. Store webhooks are verified with an HMAC-SHA256 signature and made idempotent on the store connection plus the external order ID, so a retry never creates a second invoice. See webhook retries are normal; duplicate invoices are a design bug.

Frontend

A React interface covers businesses, clients, products, quotes, invoices, payments, reports, fifteen document templates and tax guidance. Documents export as UBL 2.1 and XRechnung.

Security

There is no analytics or tracking. Webhooks are signed per connection, the API is versioned, and records stay in the deployment the owner controls. Exported electronic formats are not presented as proof of legal compliance.

Infrastructure

A single container of roughly 54 MB. Quick-start scripts install Docker where it is missing, and the Windows script sets up WSL2 with a real Docker Engine instead of Docker Desktop. A CasaOS install is documented too.

Reliability considerations

The data is one SQLite file plus the data folder, so a backup is a download from Settings or a copy of the folder. Restoring from a backup is part of the documented routine, not an afterthought.

Difficult tradeoffs

SQLite fits a single installation serving a small-business workflow; it is not a promise of unlimited concurrent writes or multi-region replication. I would split the deployment only for a measured reason such as contention or an independently scaled worker.

What I personally worked on

Lead developer across the server, the interface, the API, the installers and the documentation site.

Lessons learned

Moving to the Go and SQLite rewrite meant planning the exit from the old database: a one-time converter that leaves the old file untouched and says plainly that accounts and store connections are not copied. The story is in rewriting an invoicing app means planning the exit from the old database.

Technologies

  • Go
  • SQLite
  • React
  • TypeScript
  • Docker

Screenshots

Captured from running instances.

Mavri: frequently asked questions

What is Mavri?

Mavri is free, open-source invoicing and quote software that runs on your own server as one Docker container with a SQLite data folder.

Does Mavri cost anything?

No. It is free under the AGPL-3.0 licence, with no paid tiers or feature gating.

Can it export electronic invoices?

Yes. It exports UBL 2.1 and XRechnung. An export format alone is not proof of legal compliance, so check local rules.

Can I connect a store or script?

Yes. Mavri has a versioned REST API, signed webhooks that are safe to retry, and an MCP server.

How do I back it up or move from an older version?

Download a backup from Settings or copy the data folder. Earlier installations use the one-time mavri-legacy converter, which does not copy accounts or store connections.

Read the full article: how Mavri works →

Mavri dashboard showing billed, paid, overdue and outstanding amountsMavri invoice editor with line items, taxes and payment detailsMavri invoice list with document statusMavri quotes list where an accepted quote converts to an invoice

Expertise this project shows

Related articles

Official project links

Have something in mind?

Let’s build something useful.

Tell me about the idea, product, or workflow you’re working through.

Tap to say hello