Add a feedback form that emails the instance operator #112

Merged
kleb merged 1 commit from feat/feedback-mail into main 2026-08-24 17:02:07 +02:00
Collaborator

Adds a feedback form to Settings > General, shown only when the operator sets PLANNER_FEEDBACK_EMAIL.

Why this shape

Feedback needs a recipient. Persisting it would force the administrator surface this app has deliberately never had: a role, a read UI, moderation, unread state. Logging it fails differently, because nobody reads logs. Mailing it lands where the operator already looks, and costs one config value and no new data.

What it does

  • POST /api/feedback, session-only (API tokens are refused), rate limited to five submissions per account per hour.
  • Up to three image or text attachments, 2 MB each and 5 MB total. Images are carried inline by content id so a screenshot renders in the email body; other files are attached and listed by name.
  • The send is awaited and its failure surfaces to the sender. Every other mail.ts caller fires and forgets because a failed share notification should not fail the share, but here the send is the entire feature, and reporting success on a message that never left the process is the one way it could betray the user.
  • Only metadata is logged. The message body is the user's own words and belongs in the operator's mailbox, not in a log file that ships with the instance.
  • Reply-To is the sender's verified recovery email when they have one, so answering is just hitting reply.

The blueprint email template gains optional detail rows, inline images, and an attachment list; stripToText now turns <br> into a real newline so multi-line messages stay readable in the plaintext part. bun run preview:emails renders the new mail in all three locales.

Nothing is persisted, so there is no migration and no stored state to clean up.

Checks

bun run check green: typecheck, oxlint, eslint, oxfmt, knip, 894 bun tests, 223 vitest tests, web build within budget.

New coverage: tests/feedback.test.ts (7 cases, end to end against a fake SMTP: gating, delivery with metadata and reply-to, subject header injection via the summary, attachment type/count/size limits, rate limit), feedback cases in tests/mail.test.ts, and FeedbackSection.test.tsx (6 cases). tests/recovery.test.ts pinned /api/config to an exact object, so that assertion was updated for the new feedbackEnabled flag.

The form was also verified rendering in the real app.

Adds a feedback form to Settings > General, shown only when the operator sets `PLANNER_FEEDBACK_EMAIL`. ## Why this shape Feedback needs a recipient. Persisting it would force the administrator surface this app has deliberately never had: a role, a read UI, moderation, unread state. Logging it fails differently, because nobody reads logs. Mailing it lands where the operator already looks, and costs one config value and no new data. ## What it does - `POST /api/feedback`, session-only (API tokens are refused), rate limited to five submissions per account per hour. - Up to three image or text attachments, 2 MB each and 5 MB total. Images are carried inline by content id so a screenshot renders in the email body; other files are attached and listed by name. - The send is awaited and its failure surfaces to the sender. Every other `mail.ts` caller fires and forgets because a failed share notification should not fail the share, but here the send is the entire feature, and reporting success on a message that never left the process is the one way it could betray the user. - Only metadata is logged. The message body is the user's own words and belongs in the operator's mailbox, not in a log file that ships with the instance. - `Reply-To` is the sender's verified recovery email when they have one, so answering is just hitting reply. The blueprint email template gains optional detail rows, inline images, and an attachment list; `stripToText` now turns `<br>` into a real newline so multi-line messages stay readable in the plaintext part. `bun run preview:emails` renders the new mail in all three locales. Nothing is persisted, so there is no migration and no stored state to clean up. ## Checks `bun run check` green: typecheck, oxlint, eslint, oxfmt, knip, 894 bun tests, 223 vitest tests, web build within budget. New coverage: `tests/feedback.test.ts` (7 cases, end to end against a fake SMTP: gating, delivery with metadata and reply-to, subject header injection via the summary, attachment type/count/size limits, rate limit), feedback cases in `tests/mail.test.ts`, and `FeedbackSection.test.tsx` (6 cases). `tests/recovery.test.ts` pinned `/api/config` to an exact object, so that assertion was updated for the new `feedbackEnabled` flag. The form was also verified rendering in the real app.
Settings > General gains a feedback form when PLANNER_FEEDBACK_EMAIL is
set. Submissions are rendered into one email and never persisted, so the
instance still needs no administrator account and no inbox screen.

POST /api/feedback is session-only, rate limited to five submissions per
account per hour, and accepts up to three image or text attachments (2 MB
each, 5 MB total). Images ride inline by content id so a screenshot renders
in the body; other files are attached and listed. The send is awaited and
its failure surfaces to the sender, since reporting success on a send that
never left the process is the one way this feature could betray them. Only
metadata is logged; the message body belongs in the operator's mailbox, not
in a log file that ships with the instance.

The blueprint email template gains optional detail rows, inline images, and
an attachment list. Reply-To is set to the sender's verified recovery email
when they have one.
Collaborator

kReview review

Verdict: no findings

No findings to address in the reviewed diff.

The change appears low risk, with no concrete correctness, security, or regression issues established by the visible diff and repository evidence.

Reviewed by kReview at 1643e05f48. This comment is conservative and based only on the PR diff, metadata, and supplied repository context.

Est. cost ~$1.04 (553.3k in / 2.5k out / gpt-5.6-sol).

<!-- codex-forgejo-review --> <!-- codex-forgejo-review-head:1643e05f480f1d76f1f0fecd2f826a0ab5ceb0d9 --> ## kReview review **Verdict:** no findings No findings to address in the reviewed diff. The change appears low risk, with no concrete correctness, security, or regression issues established by the visible diff and repository evidence. _Reviewed by kReview at `1643e05f48`. This comment is conservative and based only on the PR diff, metadata, and supplied repository context._ _Est. cost ~$1.04 (553.3k in / 2.5k out / gpt-5.6-sol)._ <!-- codex-forgejo-review-state:eyJoZWFkU2hhIjoiMTY0M2UwNWY0ODBmMWQ3NmYxZjBmZWNkMmY4MjZhMGFiNWNlYjBkOSIsInN1bW1hcnkiOiJUaGUgY2hhbmdlIGFwcGVhcnMgbG93IHJpc2ssIHdpdGggbm8gY29uY3JldGUgY29ycmVjdG5lc3MsIHNlY3VyaXR5LCBvciByZWdyZXNzaW9uIGlzc3VlcyBlc3RhYmxpc2hlZCBieSB0aGUgdmlzaWJsZSBkaWZmIGFuZCByZXBvc2l0b3J5IGV2aWRlbmNlLiIsImZpbmRpbmdzIjpbXSwiY3VtdWxhdGl2ZUNvc3QiOnsidXNkIjoxLjAzNzg1NjAwMDAwMDAwMDEsImlucHV0VG9rZW5zIjo1NTMzMjMsIm91dHB1dFRva2VucyI6MjQ5MCwibW9kZWwiOiJncHQtNS42LXNvbCJ9fQ== -->
kleb merged commit 827bb797c6 into main 2026-08-24 17:02:07 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
knopersikcuo/Planner!112
No description provided.