Skip to content

Email each paid invoice as a PDF on Cloud - #1605

Merged
helpcodeai merged 2 commits into
mainfrom
feat/invoice-emails
Nov 9, 2026
Merged

helpcodeai merged 2 commits into
mainfrom
feat/invoice-emails

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

Summary

Cloud workspace admins can have every paid invoice emailed as a PDF, to the billing contact or to another address (e.g. accounting). Stripe's own receipt emails can only be switched on for the whole account, so this is per workspace and off by default. The licence site stores the setting and sends the emails (HelpCode-ai/anythingmcp-website, companion PR); this PR is the Cloud side. Relates to #1604.

Changes

  • Backend:
    • GET and PUT /api/license/invoice-email (ADMIN; Cloud only, 404 on self-hosted; PUT throttled 10/min).
    • InvoiceEmailService resolves the licence key from the caller's own workspace, as the billing portal does, and calls the licence site with the service token. The site's reply is parsed field by field and the site's error wording is never passed on.
    • InvoiceEmailDto has no workspace or licence field: the whitelist pipe rejects them.
    • New security event INVOICE_EMAIL_CHANGED, which stores the recipient's domain only.
  • Frontend:
    • An "Invoices by email" card on Settings → License, for Cloud with a billable subscription. It has a switch, a choice between the billing contact and another address, and the last invoice sent.
    • Strings in en, de and it.
  • CLI:
    • amcp invoices email [on|off] [--to <address> | --billing-contact] [--yes], with completion and usage.
    • operations.ts regenerated.
  • Docs: docs/cloud-invoices.md, plus a section in the CLI guide (en, de, it).

Cloud and self-hosted

  • Cloud: as described.
  • Self-hosted: the endpoints answer 404 and the card is never shown (hasBillableSubscription requires Cloud).

Type

  • New feature

Testing

Where Command Result
packages/backend npx tsc --noEmit -p tsconfig.json clean
packages/backend npx eslint on the changed files clean
packages/backend npx jest --maxWorkers=4 src/license src/audit 31 suites passed, 1 skipped; 550 tests passed, 9 skipped
packages/frontend npx tsc --noEmit -p tsconfig.json clean
packages/frontend npx eslint src/components/invoice-email-card.tsx clean
packages/frontend npx eslint src/app/settings/license/page.tsx 3 warnings, all on the existing loadStatus effect, not changed here
packages/frontend npx playwright test tests/e2e/invoice-email.spec.ts tests/e2e/billing-awareness.spec.ts 19 passed
packages/cli npm test 25 passed, 2 skipped (fish not installed)
repo root node scripts/generate-cli-operations.mjs --check up to date (208 operations)

New tests:

  • Backend (src/license/invoice-email.spec.ts):
    • the caller's own key and workspace are the only ones sent;
    • the DTO refuses organizationId and licenseKey;
    • ADMIN only, 404 on self-hosted;
    • the site's refusals are mapped without its wording;
    • the reply is filtered;
    • the security event metadata.
  • e2e (tests/e2e/invoice-email.spec.ts):
    • switching on;
    • saving another address and the billing contact;
    • validation;
    • the last invoice sent;
    • hidden without a subscription and on a free trial.
  • CLI: show, on with --to, --billing-contact keeping the switch, off, and the refusals (no request sent).

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • I have updated documentation
  • The tests listed above pass
  • This PR has a descriptive title

Related Issues

Relates to #1604. Deploy after the licence-site PR; until then the endpoints answer 502 "unavailable" and the card shows its load error.

@helpcodeai
helpcodeai enabled auto-merge (rebase) November 9, 2026 09:26
@helpcodeai
helpcodeai force-pushed the feat/invoice-emails branch 2 times, most recently from e1afd37 to 20d2abd Compare November 9, 2026 10:10
Settings → License gets an "Invoices by email" switch for workspace
admins, with the billing contact or another address as recipient. The
Cloud relays the choice to the licence site (service token), which stores
it on the workspace's licence and sends the emails. GET/PUT
/api/license/invoice-email take the workspace and licence from the
session only; the CLI gets amcp invoices email [on|off] [--to |
--billing-contact]. Self-hosted: 404, hidden.
@helpcodeai
helpcodeai merged commit 40b4a64 into main Nov 9, 2026
16 checks passed
@helpcodeai
helpcodeai deleted the feat/invoice-emails branch November 9, 2026 11:29
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant