A self-hosted invoicing application for freelancers and small businesses. Create invoices and quotes, manage clients, and generate PDFs.
- Create and track invoices and quotes with automatic numbering
- Client database with address and payment terms
- PDF generation with customizable branding and logo
- Tax support with per-invoice, per-client, or global defaults
- Recurring invoices for retainers and subscriptions
- SMTP email to send invoices directly to clients
- Full-text search across invoices and clients
- Built-in authentication with rate limiting
- Automatic daily backups with optional S3 storage
- Dark mode with system preference detection
- Overdue invoice highlighting and automatic status updates
- Analytics for revenue tracking and client insights
- MCP integration for Claude Desktop (separate key from REST bot API)
- Bot API key for conventional
/api/*automation with hostedSKILL.md - SQLite storage, runs anywhere
git clone https://github.com/davidtorcivia/invoice-machine.git
cd invoice-machine
docker-compose up -dOpen http://localhost:8080 and create your admin account.
For production, set environment variables to configure the port and data directory:
# Set your production values
export PORT=8085
export DATA_DIR=/nvme-mirror/apps/invoice-machine/data
export APP_BASE_URL=https://invoices.yourdomain.com
# Start the container
docker-compose up -dOr create a .env file in the project directory:
PORT=8085
DATA_DIR=/nvme-mirror/apps/invoice-machine/data
APP_BASE_URL=https://invoices.yourdomain.comThe container always listens on port 8080 internally. The PORT variable maps it to your desired external port.
- Create your admin account on the setup screen
- Go to Settings to configure your business name, address, and logo
- Add payment instructions (bank details, Venmo, etc.)
- Start creating invoices
Set these in a .env file or as environment variables. See .env.example for a complete template.
| Variable | Description | Default |
|---|---|---|
PORT |
External port mapping | 8080 |
DATA_DIR |
Directory for data storage | ./data |
APP_BASE_URL |
Base URL for the application | http://localhost:8080 |
ENVIRONMENT |
Environment mode (development/staging/production) | development |
TRASH_RETENTION_DAYS |
Days before auto-purge from trash | 90 |
| Variable | Description | Default |
|---|---|---|
INVOICE_MACHINE_ENCRYPTION_KEY |
Encryption key for sensitive data (SMTP passwords) | Required in production |
SECURE_COOKIES |
Enable secure cookies (requires HTTPS) | false |
CORS_ORIGINS |
Allowed CORS origins (comma-separated) | http://localhost:3000,http://localhost:8080 |
The encryption key protects sensitive data like SMTP credentials. In production, this key is required—the application will refuse to start without it.
Generate a secure 32-byte (64 hex characters) key using any of these methods:
Python:
python -c "import secrets; print(secrets.token_hex(32))"OpenSSL:
openssl rand -hex 32PowerShell:
-join ((1..32) | ForEach-Object { "{0:x2}" -f (Get-Random -Maximum 256) })Store the key securely and never commit it to version control. If you lose the key, any encrypted credentials (SMTP passwords) will become unreadable and must be re-entered.
| Variable | Description | Default |
|---|---|---|
DEFAULT_PAYMENT_TERMS_DAYS |
Default payment terms | 30 |
DEFAULT_CURRENCY_CODE |
Default currency | USD |
DEFAULT_ACCENT_COLOR |
PDF accent color (hex) | #16a34a |
For production deployments behind HTTPS (Cloudflare Tunnel, nginx, etc.), these settings are required:
# Required for production
INVOICE_MACHINE_ENCRYPTION_KEY=your_64_character_hex_key_here
APP_BASE_URL=https://invoices.yourdomain.com
ENVIRONMENT=production
SECURE_COOKIES=true
CORS_ORIGINS=https://invoices.yourdomain.com
# Recommended: persistent data storage
DATA_DIR=/var/lib/invoice-machine/data| Variable | Required | Description |
|---|---|---|
INVOICE_MACHINE_ENCRYPTION_KEY |
Yes | Encryption key for sensitive data (see Generating an Encryption Key) |
APP_BASE_URL |
Yes | Must match your public URL for PDF links, MCP, and hosted SKILL.md |
ENVIRONMENT |
Yes | Set to production for proper logging and defaults |
SECURE_COOKIES |
Yes | Must be true when using HTTPS |
CORS_ORIGINS |
Yes | Must match your domain to prevent CORS errors |
DATA_DIR |
Recommended | Persistent storage location outside container |
- Go to Invoices > New Invoice
- Select a client or enter details manually
- Add line items with descriptions, quantities or hours, and prices
- Set issue date and due date
- Click Create Invoice
Same as invoices, but check "This is a Quote". Quotes are numbered separately with a Q- prefix.
Click Download PDF on any invoice. PDFs regenerate automatically when the invoice changes. Filename format: [Client Name] - [Invoice Number].pdf
Format: YYYYMMDD-N where N resets daily.
- First invoice on June 23, 2025:
20250623-1 - Second invoice same day:
20250623-2 - Quotes:
Q-YYYYMMDD-N
Changing an invoice's date regenerates its number.
Invoice Machine supports optional tax with a cascade system:
- Invoice-level: Override tax settings on individual invoices
- Client-level: Set default tax for specific clients
- Global default: Configure in Settings > Tax Settings
The cascade priority is: Invoice > Client > Global. Tax is disabled by default.
To enable tax:
- Go to Settings and enable tax with your default rate
- Optionally set per-client rates in the client editor
- Override on specific invoices as needed
Set up recurring invoices for retainers, subscriptions, or regular services:
- Go to Clients > Select a client > Recurring Schedules
- Create a schedule with:
- Name (e.g., "Monthly Retainer")
- Frequency (daily, weekly, monthly, quarterly, yearly)
- Schedule day (1-31 for monthly, 0-6 for weekly)
- Line items and amounts
- Invoices are generated automatically at 2 AM UTC
You can also trigger schedules manually or pause/resume them.
Send invoices directly to clients via SMTP:
- Go to Settings > Email Configuration
- Configure your SMTP server:
- Host and port (587 for TLS, 465 for SSL)
- Username and password
- From name and email
- Click "Test Connection" to verify
- On any invoice, click "Send Email" to deliver the PDF
Works with any SMTP provider (Gmail, SendGrid, Mailgun, etc.).
Use the search bar to find invoices and clients:
- Search by invoice number, client name, or notes
- Results are ranked by relevance using full-text search
- Partial matches are supported
Invoice Machine works with Claude Desktop through MCP. You can create invoices, manage clients, and generate PDFs through natural language.
- Go to Settings > MCP Integration
- Click Generate API Key
- Copy the configuration to your Claude Desktop config:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"invoice-machine": {
"command": "npx",
"args": [
"mcp-remote",
"https://your-server.com/mcp/sse",
"--header",
"Authorization: Bearer YOUR_MCP_API_KEY"
]
}
}
}Replace your-server.com with your actual domain and YOUR_MCP_API_KEY with the key from Settings.
The MCP endpoint runs on the same port as the web app. Works with Cloudflare Tunnel or any reverse proxy.
The MCP API key is only for MCP (/mcp/*) connections from Claude Desktop.
For conventional REST API calls (/api/*), use the separate Bot API key described below.
Use a dedicated Bot API key for automation tools, scripts, and agents making standard HTTP requests.
- Go to Settings > Bot API Key
- Click Generate Bot API Key
- Save the key immediately (it is only shown once)
- Send it as a bearer token in REST API requests
Use this header on /api/* requests:
Authorization: Bearer YOUR_BOT_API_KEYInvoice Machine exposes a hosted skill file for agent setup at:
https://your-server.com/SKILL.md
curl -H "Authorization: Bearer YOUR_BOT_API_KEY" \
"https://your-server.com/api/invoices/paginated?page=1&per_page=10"If running locally with Docker, you can use stdio transport instead:
{
"mcpServers": {
"invoice-machine": {
"command": "docker",
"args": ["exec", "-i", "invoice-machine", "python", "-m", "invoice_machine.mcp.server"]
}
}
}Invoices:
- "Create an invoice for Acme Corp for website development, 40 hours at $150/hour"
- "Create a quote for logo design $500, brand guidelines $1200"
- "List all unpaid invoices from last month"
- "Mark invoice 20250115-1 as paid"
- "Generate PDF for all draft invoices"
Recurring:
- "Set up a monthly retainer for Acme Corp, $2000 on the 1st of each month"
- "Pause the recurring schedule for Client X"
- "Trigger the quarterly invoice for Big Corp now"
Analytics:
- "What's my revenue summary for 2024?"
- "Who are my top 5 clients by total paid?"
- "Show lifetime value for Acme Corp"
Email:
- "Send invoice 20250115-1 to john@acme.com"
- "Email the latest invoice to the client"
Search:
- "Search for invoices containing 'website'"
- "Find all clients named Smith"
Business Profile: get_business_profile, update_business_profile, add_payment_method, remove_payment_method
Clients: list_clients, get_client, create_client, update_client, delete_client, restore_client
Invoices: list_invoices, get_invoice, create_invoice, update_invoice, delete_invoice, restore_invoice
Line Items: add_invoice_item, update_invoice_item, remove_invoice_item
Recurring: list_recurring_schedules, get_recurring_schedule, create_recurring_schedule, update_recurring_schedule, delete_recurring_schedule, pause_recurring_schedule, resume_recurring_schedule, trigger_recurring_schedule
Search: search - Full-text search across invoices and clients
Analytics: get_revenue_summary, get_top_clients, get_client_lifetime_value
Email: send_invoice_email, test_smtp_connection
Other: generate_pdf, list_trash
invoice-machine/
├── invoice_machine/ # Python backend
│ ├── api/ # FastAPI routes
│ ├── mcp/ # MCP server
│ ├── pdf/ # PDF generation
│ ├── alembic/ # Database migrations
│ ├── database.py # SQLAlchemy models
│ ├── services.py # Business logic
│ ├── email.py # SMTP email service
│ ├── config.py # Configuration management
│ └── main.py # FastAPI app
├── frontend/ # SvelteKit frontend
├── tests/ # Test suite
│ ├── test_services.py # Service tests
│ ├── test_api.py # API tests
│ ├── test_new_features.py # Feature tests
│ └── test_security.py # Security tests
├── data/ # Runtime data (gitignored)
│ ├── invoice_machine.db # SQLite database
│ ├── backups/ # Database backups
│ ├── pdfs/ # Generated PDFs
│ └── logos/ # Uploaded logos
├── alembic.ini # Alembic configuration
├── .env.example # Environment template
└── docker-compose.yml
pip install -e ".[dev]"
python -c "import mcp; print('mcp installed')"
uvicorn invoice_machine.main:app --reload --port 8080cd frontend
npm install
npm run dev
npm run build # Production buildpytest tests/ -vInvoice Machine can automatically back up your database daily at midnight UTC.
- Go to Settings > Backup & Restore
- Enable "Automatic daily backups"
- Set retention period (default: 30 days)
Optionally upload backups to S3-compatible storage (AWS S3, Backblaze B2, MinIO):
- Enable "Upload backups to S3-compatible storage"
- Enter your endpoint URL, credentials, bucket, and region
- Click "Test S3 Connection" to verify
All data lives in the data/ directory. Copy it to back up everything. You can also create manual backups from the Settings page and download them directly.
Click the restore button on any backup to restore. A pre-restore backup is created automatically. The application needs to be restarted after restore.
Invoice Machine includes several security features:
- PBKDF2-HMAC-SHA256 password hashing with 600,000 iterations
- Password complexity requirements (minimum 8 characters with lowercase, uppercase, and digit)
- Rate limiting on login endpoints (3 attempts/minute)
- Database-backed sessions with 30-day expiration
- CSRF protection using double-submit cookie pattern
- Configurable secure cookies for HTTPS deployments
- SMTP passwords and other sensitive data encrypted at rest using Fernet (AES-128-CBC)
- Encryption key required in production (application refuses to start without it)
- Key derivation uses PBKDF2-HMAC-SHA256 with unique salt
- Path traversal prevention on file operations
- Email header injection protection
- Image upload validation (magic bytes + extension)
- SQL injection prevention via SQLAlchemy ORM
- FTS5 query sanitization
- Docker container runs as non-root user (UID 1000)
- Minimal attack surface with production-only dependencies
- Set encryption key: Generate and set
INVOICE_MACHINE_ENCRYPTION_KEY(see Generating an Encryption Key) - Use HTTPS: Set
SECURE_COOKIES=truewhen behind HTTPS - Restrict CORS: Set
CORS_ORIGINSto your actual domain only - Regular backups: Enable automatic backups with S3 for offsite storage
- Access control: Use Cloudflare Access or similar for additional protection
- Keep updated: Pull latest Docker images regularly
For remote access, use a reverse proxy. Example with Cloudflare Tunnel:
cloudflared tunnel create invoice-machine- Configure Access policy in Cloudflare dashboard
- Set
APP_BASE_URLin.env - Set
SECURE_COOKIES=truefor HTTPS
version: '3.8'
services:
invoice-machine:
image: invoice-machine:latest
container_name: invoice-machine
ports:
- "8080:8080"
environment:
# Required for production
- INVOICE_MACHINE_ENCRYPTION_KEY=${INVOICE_MACHINE_ENCRYPTION_KEY}
- APP_BASE_URL=https://invoices.yourdomain.com
- ENVIRONMENT=production
- SECURE_COOKIES=true
- CORS_ORIGINS=https://invoices.yourdomain.com
# Database (uses container path)
- DATABASE_URL=sqlite+aiosqlite:////app/data/invoice_machine.db
- DATA_DIR=/app/data
# Optional: customize defaults
- DEFAULT_PAYMENT_TERMS_DAYS=30
- DEFAULT_CURRENCY_CODE=USD
- TRASH_RETENTION_DAYS=90
volumes:
- /var/lib/invoice-machine/data:/app/data
restart: unless-stopped
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
interval: 30s
timeout: 3s
retries: 3Important notes:
- The
DATABASE_URLuses 4 slashes (////app/data/) because SQLite URLs need 3 slashes plus the absolute path - The volume mount (
/var/lib/invoice-machine/data:/app/data) persists all data including the database, PDFs, logos, and backups - Set
container_nametoinvoice-machineif using MCP withdocker exec
MIT License. See LICENSE for details.