Self-hosted expense and travel management for teams. Snap a receipt on the road, log it against a trip, submit it when you land β and let finance approve the whole thing from one dashboard. No spreadsheet graveyards, no shoebox of crumpled receipts.
.NET 9 Β· ASP.NET Core Β· Next.js Β· Expo Β· PostgreSQL Β· RabbitMQ Β· MinIO Β· one docker compose up
| Where | Link | What you'll see |
|---|---|---|
| π Web app | expenn.osas.cloud | Workspaces, trips, the expense review queue, and the document vault |
| βοΈ API | api.expenn.osas.cloud | The live .NET API the web and mobile clients talk to |
| π Help center | expenn.osas.cloud/docs | End-user guides: getting started, trips, expenses, documents, mobile |
π‘ Sign-in is passwordless. Enter an email, get a one-time code, and you're in β then pick a personal account or a company workspace. Self-hosting the stack gives you both sides of the flow (traveler and approver) on your own machine in a couple of minutes.
For travelers π β capture receipts on the go, log expenses against a trip, store passports and itineraries in the document vault, and submit for reimbursement when you're back.
For finance and admins π β create and assign trips, review submitted expenses, approve or reject with a note, and track team spend across every trip from one dashboard.
capture βββΆ log against a trip βββΆ submit βββΆ review βββΆ approve βββΆ reimburse
πΈ π§³ π€ π β
πΈ
| Surface | Highlights | |
|---|---|---|
| π | Web | Workspace dashboards, trip management, the review queue, Fumadocs help center |
| π | Traveler | Personal expense log, trip view, document vault, reimbursement status |
| π± | Mobile | Expo app with OTP sign-in, camera receipt capture, biometrics, offline-first sync |
| βοΈ | API | .NET 9 API β owns auth, roles, business rules, and the PostgreSQL schema |
| π¬ | Async | MassTransit over RabbitMQ for background work and notifications |
| π¦ | Storage | MinIO (S3-compatible) for receipts and travel documents |
| Surface | Stack |
|---|---|
| API | .NET 9, ASP.NET Core, EF Core, PostgreSQL, MassTransit |
| Web | Next.js, React, TypeScript, Fumadocs |
| Mobile | Expo, React Native, Fastlane |
| Infrastructure | MinIO, RabbitMQ, Docker Compose |
| Delivery | GitHub Actions, GHCR, Play Console, TestFlight |
Docker Compose is the supported full-stack development path. It starts PostgreSQL, MinIO, RabbitMQ, the API, and the web app, and creates the object-storage bucket automatically.
git clone https://github.com/usmhic/expenn.git && cd expenn
cp .env.example .env # PowerShell: Copy-Item .env.example .env
# Fill the secrets marked FILL ME at the bottom of .env, then:
docker compose up --build.env.example already carries working localhost defaults for everything that is not a secret, so
the only values you need to invent are the passwords and the JWT secret. .env is gitignored;
never commit it.
β Grab a coffee for the first build, then open:
| Service | URL |
|---|---|
| π Web | http://localhost:3000 |
| βοΈ API | http://localhost:5000 |
| β€οΈ API health | http://localhost:5000/health |
| π¦ MinIO console | http://localhost:9001 |
| π RabbitMQ console | http://localhost:15672 |
| π PostgreSQL | localhost:5432 |
Useful commands:
docker compose logs -f api web
docker compose down
docker compose down --volumes # also deletes local dataBrowser / Mobile app
β HTTPS
βΌ
βββββββββββββββββββββ
β Next.js (web) β :3000
β Dashboards ββββββββββββββΆ ASP.NET Core (api) :5000
β Traveler + Adminβ EF Core Β· JWT Β· roles
βββββββββββββββββββββ MinIO Β· RabbitMQ Β· PostgreSQL
Mobile (Expo) ββββ Bearer token ββββΆ
The API is the single source of truth for data, auth, and business rules. Full breakdown in ARCHITECTURE.md.
| Path | Purpose |
|---|---|
api/Expenn.Api/ |
.NET 9 API; owns auth, business logic, and the PostgreSQL schema |
web/ |
Next.js web app |
mobile/ |
Expo SDK 54 mobile client |
docker-compose.yml |
Complete local/deployment stack |
.env.example |
The only environment template |
All Compose configuration lives in the ignored root .env. The tracked .env.example lists the
required variable names with intentionally empty values; fill the local copy before starting the
stack.
| Variable | Purpose |
|---|---|
APP_URL |
Public web URL and allowed browser origin |
API_URL |
Public API URL compiled into browser clients |
STORAGE_URL |
Public MinIO URL returned for uploaded files |
POSTGRES_PASSWORD |
PostgreSQL password |
MINIO_PASSWORD |
MinIO password shared with the API |
RABBITMQ_PASSWORD |
RabbitMQ password shared with the API |
JWT_SECRET |
API token-signing secret |
π± The App Store and Play Store listings aren't live yet β signed builds already ship through TestFlight and Play Console from CI, and public listings are next. Until then, running the traveler app takes about a minute:
docker compose up -d db minio minio-init rabbitmq api # backend in Compose
cd mobile && pnpm install && pnpm start # app on your hostScan the QR code with Expo Go and you're in. Set EXPO_PUBLIC_API_URL in your shell when a
simulator or physical device cannot reach http://localhost:5000 β use the computer's LAN address
for a physical device. See mobile/README.md.
| Identifier | |
|---|---|
| π iOS bundle | com.expenn.traveler |
| π€ Android package | com.osascloud.expenn |
Use production secrets, set all three public URLs to HTTPS endpoints, and run:
docker compose up -d --buildPlace a TLS-terminating reverse proxy in front of web, API, and object storage. Do not commit the
production .env or expose PostgreSQL, RabbitMQ, or MinIO administration ports publicly; restrict
those published ports in the deployment platform or a production override.
| Workflow | Purpose |
|---|---|
api-ci.yml |
Restore/build the .NET API, validate Compose, build/publish the API image |
web-ci.yml |
Type-check, lint, build, publish, and smoke-test the web image |
mobile-android-dev.yml |
Type-check, signed APK, distribute to testers via Firebase App Distribution (dev) |
mobile-ios-dev.yml |
Signed Ad Hoc IPA, distribute to testers via Firebase App Distribution (manual) |
mobile-android-release.yml |
Signed AAB, upload to Google Play Console (main) |
mobile-ios-release.yml |
Signed IPA, upload to App Store Connect / TestFlight (main) |
release.yml |
Create a GitHub release from a vX.Y.Z tag |
Container images live at ghcr.io/usmhic/expenn-api and ghcr.io/usmhic/expenn-web. Mobile
credentials remain in GitHub Secrets; see the mobile guide.
| Doc | What's in it |
|---|---|
| Architecture | Service boundaries, data flow, and directory purposes |
| API guide | .NET modules, migrations, and optional integrations |
| Web guide | Next.js app, workspace routing, and the help center |
| Mobile guide | Expo setup, device testing, Fastlane, and releases |
| Mobile delivery | Signing secrets, App Distribution, and store submission |
| Engineering standards | Shared conventions across every usmhic project |
| Package naming | Public package, namespace, and app identifiers |
| Coding-agent guide | Repository map, commands, and guardrails |
| Security policy | Private vulnerability reporting and deployment notes |
Issues and pull requests are welcome β focused fixes, bold ideas, and thoughtful docs improvements all count. Start with CONTRIBUTING.md and follow the Code of Conduct.