From first setup to a sent invoice.
Practical guides for running Invoice Studio day to day, plus an exact self-hosting reference for operators. Every behavior here is grounded in the current product.
Product overview
Invoice Studio is a multi-workspace invoicing app for freelancers, founders, and small teams. It saves customer companies, payout accounts, and sender identities, then combines them into GST-ready INR invoices and generated PDF documents.
Core concepts
| Concept | What it means | Choose it when |
|---|---|---|
| Organization | An isolated workspace containing its own customers, profiles, invoices, documents, and members. | You operate a separate business or need a hard data boundary. |
| Company | The customer being billed. | You want to reuse a customer’s address, GSTIN, and delivery email. |
| Sender profile | The identity printed on an invoice, including PAN, GSTIN, GST rate, branding, email, and signature. | You bill through different legal identities or tax contexts. |
| Bank details | The payout instructions printed on the invoice. | You receive money into different accounts or currencies. |
| Draft / issued | Drafts remain editable. Issued invoices are final in the current product. | Use draft while facts may change; issue only after review. |
It does not process payments, reconcile bank transactions, create recurring invoices, support multiple currencies, or keep an audit-history screen. “Paid” is a manual tracking flag.
Your first successful workflow
The reliable order is sender → bank → customer → invoice. This avoids reaching the invoice form without the reusable records it expects.
- 1
Create an account and organization
Sign up with email and a password of at least 6 characters, or use Google if the deployment enables it. Name your first organization.
- 2
Create a sender profile
Open Sender Profiles. Add the email that should receive customer replies, your tax identity, a GST rate, invoice headings, and an optional PNG signature.
- 3
Add payout details
Open Bank Details. Save the account name, number, bank, branch, IFSC and, when needed, SWIFT. A tag helps distinguish similar accounts.
- 4
Save the customer
Open Company Details. Name is required. Add the customer email before you intend to send from the app.
- 5
Create and issue
Open Invoice Workspace, choose the saved records, set an issue date, add descriptions and amounts, review GST and total, then choose Issue Invoice.
- 6
Generate or send the PDF
Preview HTML first if desired. Generate a PDF for the Documents library, or send it to the company email. Sending always renders a fresh PDF.
Workspaces and switching
Organizations separate all operational records. Your most recently selected organization is saved in this browser as activeOrganizationId. With one membership the app selects it automatically; with several, it asks you to choose.
How to create or switch
- 1
Open the organization switcher
Use the workspace control in the dashboard sidebar.
- 2
Select an existing workspace
The dashboard reloads data in that organization context.
- 3
Or create another
Choose the new-organization option and enter a non-empty name. The creator becomes its owner.
Companies, sender profiles, bank details, invoices, PDFs, and team membership stay inside their organization. Recreate a record when it is genuinely shared; there is no cross-workspace copy operation.
Customer companies
Use companies for reusable bill-to information. Do not create a company for your own identity—that belongs in Sender Profiles.
| UI field | API property | Required | Behavior |
|---|---|---|---|
| Name | name | Yes | Trimmed before storage; the only server-required company field. |
| GSTIN | gstin | No | Printed with the customer address; format is not validated. |
email | No for saving; required for sending | Browser validates email shape. Server requires a non-empty saved value before send. | |
| Address | address | No | Line breaks are retained in the invoice address block. |
| Notes | notes | No | Internal saved text; not rendered on invoices. |
How to: add details and select Save Company. Use Edit to update the same record. Duplicate fills a new unsaved form; save it to create a separate company. Search currently matches company name only.
Bank details
A bank record supplies payment instructions on the generated invoice. Choose a specific record per invoice. If an invoice has none selected, rendering falls back to the newest bank record in the organization.
| UI field | API property | Default | Notes |
|---|---|---|---|
| Account Name | accountName | empty | Printed as account holder. |
| Account Number | accountNumber | empty | Stored as text; no checksum or masking. |
| Bank Name | bankName | empty | Printed in payment details. |
| Branch | branch | empty | Printed in payment details. |
| IFSC | ifsc | empty | No format validation. |
| SWIFT | swift | empty | The row is omitted from the PDF when empty. |
| Tag | tag | empty | Organizational label such as Primary or USD. |
| Tag Color | tagColor | #0f766e | Five presets plus a custom color picker. |
| Notes | notes | empty | Internal; not printed. |
When not to use it: do not place card numbers, passwords, or online-banking secrets here. These fields are shared with every member who can read the organization.
Sender profiles
Sender profiles let one organization bill from different identities. The selected profile controls GST calculation and the identity, tax, branding, signature, and reply address used in the invoice.
| UI field | API property | Default / validation | Effect |
|---|---|---|---|
| Tag | tag | empty | Picker label only. |
| Tag Color | tagColor | #0f766e | Visual distinction in pickers. |
| Sender Name | senderName | optional | Shown near the signature and used as the invoice-email sender name fallback. |
email | Required by UI and for sending | Lowercased; replies to invoice emails go here. | |
| PAN | pan | empty | Printed with the sender tax block. |
| GSTIN | gstin | empty | Printed when the PAN block is present. |
| Invoice Header | headerName | empty | Main invoice heading/brand text. |
| Invoice Tagline | headerTagline | empty | Supporting header text. |
| GST Rate (%) | gstRate | 18 | UI minimum 0 and step 0.01. Server does not enforce a maximum. |
| Invoice Footer | footerText | PDF fallback: “Thank You for Your Business” | Footer printed on the PDF. |
| Signature | signaturePath | optional PNG only | PNG data URL, valid PNG bytes, .png filename, maximum 5 MB. |
GST is applied to the complete subtotal. Use 0 for a zero-tax invoice. The app does not model per-line tax rates, tax categories, discounts, withholding, or cess.
Create an invoice
Invoice numbers are assigned automatically per organization as INV-001, INV-002, and so on. The visible invoice-number field is read-only in practice: the server assigns the committed number.
| Field | Required | Default | Validation and calculation |
|---|---|---|---|
| Company | Yes | none | Must reference a company in the active organization. |
| Bank Details | No | none selected | If omitted, PDF rendering uses the newest bank record. |
| Sender Profile | No | none selected | If omitted, calculation/rendering use the newest profile; GST defaults to 18 if no usable profile rate exists. |
| Issue Date | Yes | today when the form opens | Sent as a date string. |
| Line description | UI: yes | one empty row | Trimmed by the server. |
| Line amount | UI: yes | 0 | Converted to a number. Server does not reject negative values. |
| Status | Yes by action | issued unless explicitly draft | Only the exact value `draft` stays editable. |
Draft versus issued
| Action | Use when | Result |
|---|---|---|
| Save Draft | Details may still change. | Number is reserved and the invoice can be edited later. PDF generation and sending are blocked. |
| Issue Invoice | The customer, dates, lines, and tax are final. | The invoice becomes immutable in the current app and can be rendered or sent. |
subtotal = sum(item.amount), GST = subtotal × profile GST rate / 100, and total = subtotal + GST. Currency formatting and amount-in-words are INR/rupees.
Manage invoices
The invoice list supports company, draft/issued, paid/unpaid, and issue-date-range filters. Filters affect the current browser view; they are not saved to the server.
How to handle common jobs
- 1
Edit a draft
Choose the edit action on a Draft row, change fields, then save as draft or issue. Issued rows cannot be edited.
- 2
Preview before PDF
Open the HTML preview. It is membership-protected and uses the latest related records currently saved.
- 3
Generate a PDF
Use Generate PDF on an issued invoice. A fresh PDF is uploaded to object storage and linked to the invoice.
- 4
Send an invoice
Ensure both customer and sender-profile emails exist. Sending renders a fresh PDF, emails it as an attachment, and saves the same generated document.
- 5
Track payment
Toggle paid/unpaid manually. Marking paid stores the current timestamp; marking unpaid clears it. No payment is collected.
Documents
Documents lists invoices with a currently stored PDF, newest first. Each entry includes its invoice number, customer, total, creation time, and short-lived signed view/download links.
How to delete a PDF: choose Delete in Documents. This removes the object and clears the invoice’s PDF link; it does not delete the invoice. Generate again from the issued invoice when needed.
Generated view and download URLs expire after one hour by default. Reload Documents to receive fresh links.
Team members
Only owners can open Team, invite people, change roles, remove members, or revoke pending invitations. Invitation links expire after one hour. Existing users accept and then sign in; new users create a password of at least 6 characters during acceptance.
- 1
Invite
Enter the person’s email and choose Read, Edit, or Owner. Avoid Owner unless they must administer access.
- 2
Wait for acceptance
The pending invitation remains visible to owners and can be revoked before acceptance.
- 3
Adjust access
Change a member’s role as responsibilities change, or remove the membership. Their account and other organizations remain intact.
Roles and permissions
| Capability | Read | Edit | Owner |
|---|---|---|---|
| View companies, profiles, bank details, invoices, and documents | Yes | Yes | Yes |
| Create or update operational records | No | Yes | Yes |
| Generate/send PDFs, change payment state, delete stored PDFs | No | Yes | Yes |
| View and administer members/invitations | No | No | Yes |
The database records creation times and invoice payment timestamps, but the product has no user-facing audit log of who changed what. If you require compliance-grade history, add it before relying on the app for that purpose.
Sign-in and recovery
Email/password accounts require a password of at least 6 characters. Sessions use an HTTP-only sid cookie lasting 30 days, SameSite=Lax, and Secure only when NODE_ENV=production. Google sign-in appears only when a client ID is configured.
Password-reset responses do not disclose whether an email exists. Reset links expire after one hour and can be used once. Logging out deletes the server session and clears the cookie.
Integrations and failure behavior
| Integration | Configuration | Precedence / failure behavior |
|---|---|---|
| MySQL | DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME | Required at startup. Connection or schema initialization failure stops the server. |
| S3-compatible storage | S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET_NAME | Required when uploading signatures or PDFs. Missing credentials fail that operation; signed URLs default to 1 hour. |
| Browserless / Chromium | PUPPETEER_BROWSER_URL | Required for PDF and send. An empty value returns “PUPPETEER_BROWSER_URL is not configured.” |
| Resend | RESEND_API_KEY, EMAIL_FROM, EMAIL_REPLY_TO, CONTACT_EMAIL | When the API key is absent, mail is logged and the operation reports success; nothing is delivered. Invoice replies override the default reply-to with the selected sender email. |
| Google Identity | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Empty client ID hides the button. Invalid/missing credentials make Google sign-in fail without affecting password sign-in. |
Troubleshooting
| Symptom | Likely cause | Concrete fix |
|---|---|---|
| “Missing organization context” | No active organization was selected or browser storage points to an unavailable one. | Return to the organization picker and choose a workspace. |
| “Edit permission is required” | The member has Read access. | Ask an owner to assign Edit only if changes are needed. |
| PDF generation fails | Browser WebSocket missing/unreachable, S3 misconfigured, or Chromium failed. | Verify PUPPETEER_BROWSER_URL from the app container, Browserless health/token, and S3 credentials/bucket permissions. |
| Invoice email never arrives but send says success | RESEND_API_KEY is empty, so email is log-only. | Configure Resend, verify EMAIL_FROM, then send again. |
| “Add an email address…” | Customer or selected sender profile lacks email. | Edit the corresponding saved record, then retry. |
| Cannot edit an invoice | It is issued. | The implementation intentionally allows edits only on drafts; create a new invoice for corrections. |
| Signature upload rejected | Wrong extension/MIME, invalid PNG bytes, or file exceeds 5 MB. | Export a genuine PNG named .png at 5 MB or less. |
| Old/stale document URL | The signed S3 URL expired after one hour. | Reload Documents or regenerate the PDF. |
| Server will not start | MySQL is unreachable or the user cannot create/use the database. | Test network/DNS and grant access to DB_NAME; inspect container logs. |
| Invite/reset link rejected | The link is expired or already used. | Issue a new invitation or password-reset request; links last one hour. |
Known limits and precise behavior
The current product has no invoice deletion, issued-invoice correction, recurring billing, payment gateway, automated reminders, per-line tax, discounts, multiple currencies, data import/export, workspace deletion, member activity audit, API-token system, or user-managed secret vault. These are intentionally not documented as available features.
Some database columns are more permissive than the UI: company phone exists server-side but has no current UI field; due date exists in storage but the current invoice form does not expose it; server-side tax and amount validation is limited. Operators should not treat database acceptance as a supported user workflow.
Frontend request bodies use camelCase such as accountName, personalInfoId, and gstRate. Database rows and most response properties use snake_case such as account_name, personal_info_id, and gst_rate.