Reference
Changelog
InvoiceGuard follows semver. Newest first.
[0.7.0] - 2026-10-14
Per-client dunning policies — the first agency-mode slice. Each client can now have its own dunning stage thresholds via invoiceguard client set-dunning-policy <id> [--day1 N] [--day7 N] [--day15 N] [--reset]: a positive number sets a custom days-overdue threshold, 0 disables the stage for that client, and thresholds must keep escalation order (day1 → day7 → day15). check-due resolves the policy per invoice, so one client can escalate faster while another is never auto-dunned; an invoice whose client has every stage disabled is reported as skipped dunning (invoice #N) instead of silently vanishing. client list shows a [dunning ...] marker for clients with overrides. Existing databases are migrated additively on connect (new columns, NULL = global default). Only when a stage fires changes — templates, subjects, and idempotency are untouched; multi-freelancer workspaces remain unshipped.
[0.6.5] - 2026-10-13
Dunning send-failure isolation: one invoice's email send failing no longer aborts the whole check-due run. Verified: a transient SMTP failure on the first overdue invoice propagated out and killed the run, so every other overdue invoice never got its stage. Now a failed send is reported as send_error, nothing is recorded for that invoice (the stage retries on the next run), and the rest of the batch still goes out.
[0.6.4] - 2026-10-12
Webhook void-safety: the dashboard's Stripe webhook handler (POST /webhooks/stripe) no longer records a payment or changes status when the matching invoice was voided. Verified: a void invoice came back as paid with a stripe ledger row — now void is terminal on the webhook path, matching the core record_payment() rule that refuses payments on void invoices. Honest scope: only the webhook path changed; already-paid invoices stay idempotent.
[0.6.3] - 2026-10-11
Dunning robustness: check-due used to crash the whole collections run (ValueError: recipient address is empty) when any overdue invoice's client had no email on file — easy to hit, since client add makes --email optional. Now such invoices are skipped with a reported reason, nothing is recorded for them, and every other overdue invoice still gets its email. New: invoiceguard client set-email <id> [--email ...] to add or clear a client's email (dunning) after creation.
[0.6.2] - 2026-10-10
Contract deposit fix: the deposit figure printed in the generated contract now always matches the quoted (and charged) deposit. The contract previously used float + banker's rounding, so a $10.05 project at 50% said $5.02 in the contract while the CLI quoted and charged $5.03. It now uses Decimal half-up, the same money rule as CLI amounts and late fees. Honest scope: only newly generated contract text changes, only on half-cent boundaries; stored contracts and invoices are untouched.
[0.6.1] - 2026-10-09
CLI money-parsing fix: --amount values are parsed with Decimal half-up instead of float + round(), so --amount 1.005 records 101 cents, not 100; non-positive or non-numeric amounts are rejected before anything is written. Honest scope: only CLI-entered amounts changed; stored cents and Stripe session amounts were already exact.
[0.6.0] - 2026-10-08
Receivables report: invoiceguard invoice report [--as-of YYYY-MM-DD] — read-only aging over open invoices (Current / 1-30 / 31-60 / 61-90 / 90+ day buckets, per-client totals, accrued late fees via the same §3 calculator as invoice late-fees), with --csv / --payments-csv exports for accounting. "Outstanding" matches the dashboard totals; drafts, paid, and void invoices are excluded. See CLI reference. Honest scope: read-only; fees are computed on the current outstanding balance as of the report date.
[0.5.0] - 2026-10-07
SMS / WhatsApp escalation: the day-15 formal notice can now also reach the client as a short message via Twilio (SMS or WhatsApp), recorded in a new message_events ledger. New: invoice notify <id> [--channel sms|whatsapp] [--dry-run], client add --phone, client set-phone. See Dunning engine and Configuration. Honest scope: needs your own Twilio account; no live Twilio call was verified in the build environment.
[0.4.0] - 2026-10-06
Late-fee accrual calculator: the contract has always promised "{late_fee_pct}% per month, compounding monthly" after the grace period — now InvoiceGuard computes the actual number.
Added
invoiceguard invoice late-fees <id> [--as-of YYYY-MM-DD]— accrual breakdown: due date, grace window, rate, months billed, accrued fees, total due. Fees start accruing the day after the grace period ends; each started calendar month charges one full monthly fee, compounding on the running balance, rounded half-up to the cent.- Dashboard invoice detail page: "Accrued late fees" and "Total with fees" rows under Contract & terms.
- New dunning template variables:
{late_fee_due}and{total_with_late_fees}. The shippedday15.mdformal notice now quotes the actual accrued fees and total due.
Honest scope (late fees)
- A partial month counts as a full month; the calculator uses the current outstanding balance and does not retroactively amortize mid-accrual payments. Late-fee enforceability varies by jurisdiction.
[0.3.0] - 2026-10-05
Real e-signature: clients can now sign the contract electronically instead of (or in addition to) acknowledging via the paid deposit link.
Added
- E-signature capture:
invoiceguard project sign-request <id>creates a one-time, single-use signing link (/sign/<token>, served by the dashboard). The client reads the rendered contract, types their full name, and draws a signature on a canvas (mouse or touch). - Tamper evidence: at signing time the exact signed contract text is hashed (SHA-256) and stored with the project, together with the signer's name and timestamp — proving what was signed, not just that something was signed.
- Signing flips
contract_ackto 1 (a signed contract IS the acknowledgment), withcontract_ack_at= signing time. The request token is invalidated after use; re-requesting while a request is pending returns the same link. invoiceguard project sign-status <id>shows pending/signed state, signer, timestamp, and the contract hash. Dashboard signing receipt page shows the captured signature and the hash.
Changed
- Contract §4 (Sign-off) now describes the electronic signature process instead of the v1 deposit-link acknowledgment.
project ack(manual acknowledgment) is kept as a fallback path.
Honest scope (e-signature)
- This is a browser-based typed/drawn signature captured by InvoiceGuard and stored locally — NOT a qualified third-party e-signature service (DocuSign/HelloSign). Legal weight varies by jurisdiction.
[0.2.0] - 2026-10-04
Partial payments: invoices no longer have to be all-or-nothing.
Added
- Payment ledger: new
paymentstable (additive migration; pre-0.2.0 paid invoices get a labeled backfill row on first open, so old databases keep working) invoice record-payment <id> --amount <dollars> [--note ...] [--method ...]— record a partial payment; a payment that clears the balance marks the invoicepaid, otherwise the invoice becomespartially-paid- Dashboard: per-invoice Paid/Outstanding amounts, a payments ledger on the invoice detail page, payment events in the timeline, a new
partially-paidstatus filter chip, and totals computed from outstanding balances - Dunning emails now show paid-so-far and the remaining balance (
{paid}/{outstanding}template variables; the three built-in templates use them)
Changed
invoice mark-paidnow records the full outstanding balance in the ledger (was: status flip with no payment record); it errors instead of silently re-marking an already-paid or void invoice- Stripe webhooks record the session amount in the ledger (method
stripe); a session for less than the balance leaves the invoicepartially-paidinstead ofpaid - Dashboard totals: Collected is now the sum of recorded payments, Outstanding/Overdue are sums of outstanding balances (identical numbers for pre-0.2.0 databases thanks to the backfill)
[0.1.0] - 2026-10-02
First release on PyPI (pip install invoiceguard) and GitHub.
Added
- CLI:
init,client add/list,project create/list/ack,invoice create/list/mark-paid/void,check-due,dashboard - Stripe deposit payment links per project (test-mode friendly), with
invoiceguard_invoice_idmetadata so webhook payments map back to invoices - Late-fee contract generator: deposit + late-fee clause markdown, client acknowledgment recorded when the deposit link is accepted
- Automated 3-stage dunning: day-1 polite nudge → day-7 firm reminder → day-15 formal notice quoting the late-fee clause; idempotent, cron-friendly (
check-due), editable markdown templates - Localhost web dashboard: invoice list with status filters and escalation stages, per-invoice detail with chronological timeline, totals/outstanding/overdue/collected, Stripe webhook receiver that marks invoices paid
- SQLite persistence (shared schema between CLI and dashboard), Docker Compose setup
Fixed
- Dashboard Stripe webhook secret resolution. The README told users to set
stripe_webhook_secretin the config (orINVOICEGUARD_STRIPE_WEBHOOK_SECRET), but the dashboard only honored the bareSTRIPE_WEBHOOK_SECRETenv var — webhooks returned 500 for anyone following the docs. The endpoint now resolves, in order:INVOICEGUARD_STRIPE_WEBHOOK_SECRETenv →stripe_webhook_secretin the config file → legacySTRIPE_WEBHOOK_SECRETenv (still supported). Placeholder values (whsec_...) count as unconfigured. Regression tests cover all four cases.
Honest scope (v1)
- The "signature" is a client-acknowledgment checkbox (deposit-link acceptance), not a legal e-signature — real e-signature is on the roadmap.
- Email deliverability is the user's own SMTP; late-fee enforceability varies by jurisdiction.