feat(emails): make `to` and `subject` optional on POST /api/emails
Implements the "make `to` optional on `POST /api/emails`" follow-up enhancement on chat#1815. Fulfills the merged contract docs#252.
What changed
lib/emails/validateSendEmailBody.ts:
tois now optional insendEmailBodySchema(staysminItems: 1when provided).- When
tois omitted, resolve the authenticated account's own email(s) viaselectAccountEmails({ accountIds })and use them as the recipients. - 400 when
tois omitted and the account has no email on file. - The existing recipient restriction (403 without a payment method) is unchanged and now runs on the resolved recipients — the account's own email is always allowed, so the default never trips it; a stranger in
ccstill does. ValidatedSendEmailRequest.tois now always a populatedstring[], soprocessAndSendEmail(which requiresto) is unaffected.
Why
So a caller can "email me this" without restating their own address — the common scheduled-report case. This also lets /api/emails fully subsume /api/notifications (self-send), which chat#1815 then deletes.
Tests (TDD, RED→GREEN)
Updated lib/emails/__tests__/validateSendEmailBody.test.ts:
- defaults
toto the account's own email when omitted - includes every account email when the account has more than one
- 400 when omitted and no account email on file
- does not resolve account emails when
tois provided - (kept) empty
to: []still 400s viaminItems: 1
pnpm exec vitest run lib/emails → 129 passed. tsc --noEmit adds no new errors (the 212 errors on test are pre-existing test-file type noise in lib/tasks / lib/trigger, excluded by next build). Lint clean on changed files. Preview verification to follow as a PR comment.
Merge order
docs#252 (merged contract) → this.
🤖 Generated with Claude Code
Summary by cubic
Make to and subject optional on POST /api/emails; when omitted, recipients default to the authenticated account’s email(s) and the subject is derived from the body or falls back to "Message from Recoup" (docs#252 / chat#1815).
-
New Features
tooptional; defaults to the account’s email(s). 400 if the account has none.- Recipient restriction runs on resolved recipients; the account’s own email is always allowed.
subjectoptional; uses provided value (trimmed), else the body’s first heading/line (text preferred, then HTML with tags stripped), else "Message from Recoup". Derived subjects are capped at 120 chars.- Validated responses always return a populated
toarray andsubjectstring.
-
Refactors
- Extracted
firstMeaningfulLineandstripHtmlinto separate modules with unit tests;resolveEmailSubjectimports them. No behavior change.
- Extracted
Summary by CodeRabbit
-
New Features
- Email sending now supports leaving the recipient list and subject blank in some cases.
- A subject is automatically generated from the message content when one isn’t provided.
-
Bug Fixes
- Email messages now fall back to sensible defaults for recipients and subject lines, helping prevent send failures.
- Recipient checks now use the final resolved addresses before sending.