diff --git a/docs/plans/2026-02-18-feedback-form-implementation.md b/docs/plans/2026-02-18-feedback-form-implementation.md
new file mode 100644
index 00000000..80135d1d
--- /dev/null
+++ b/docs/plans/2026-02-18-feedback-form-implementation.md
@@ -0,0 +1,817 @@
+# Feedback Form Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Add a feedback form page where logged-in users can submit feedback that gets emailed to a configurable address via the existing Resend infrastructure.
+
+**Architecture:** New `POST /feedback` backend endpoint validates input and sends an HTML email via the existing `EmailService`. Frontend is a single `FeedbackPage.tsx` form page accessible from the sidebar nav and account settings. No database storage — email-only.
+
+**Tech Stack:** FastAPI + Pydantic (backend), React + TypeScript + Tailwind (frontend), Resend (email delivery), slowapi (rate limiting)
+
+**Design doc:** `docs/plans/2026-02-18-feedback-form-design.md`
+
+---
+
+## Task 1: Backend Schema
+
+**Files:**
+- Create: `backend/app/schemas/feedback.py`
+
+**Step 1: Create the Pydantic schema**
+
+```python
+from enum import Enum
+from pydantic import BaseModel, EmailStr, Field
+
+
+class FeedbackType(str, Enum):
+ BUG_REPORT = "Bug Report"
+ FEATURE_REQUEST = "Feature Request"
+ USABILITY_ISSUE = "Usability Issue"
+ DOCUMENTATION = "Documentation"
+ GENERAL = "General Feedback"
+
+
+class FeedbackSubmission(BaseModel):
+ email: EmailStr
+ feedback_type: FeedbackType
+ message: str = Field(..., min_length=10, max_length=5000)
+
+
+class FeedbackResponse(BaseModel):
+ success: bool
+ message: str
+```
+
+**Step 2: Commit**
+
+```bash
+git add backend/app/schemas/feedback.py
+git commit -m "feat: add feedback submission schema"
+```
+
+---
+
+## Task 2: Config — Add FEEDBACK_EMAIL
+
+**Files:**
+- Modify: `backend/app/core/config.py`
+
+**Step 1: Add the FEEDBACK_EMAIL setting**
+
+In `backend/app/core/config.py`, add this line in the `Settings` class after the `FROM_EMAIL` line (line 57):
+
+```python
+ FEEDBACK_EMAIL: Optional[str] = None
+```
+
+**Step 2: Commit**
+
+```bash
+git add backend/app/core/config.py
+git commit -m "feat: add FEEDBACK_EMAIL config setting"
+```
+
+---
+
+## Task 3: Email Service — Add send_feedback_email
+
+**Files:**
+- Modify: `backend/app/core/email.py`
+
+**Step 1: Add the send_feedback_email method**
+
+Add this method to the `EmailService` class (after `send_account_invite_email`, before the helper functions):
+
+```python
+ @staticmethod
+ async def send_feedback_email(
+ to_email: str,
+ reply_to_email: str,
+ feedback_type: str,
+ message: str,
+ user_email: str,
+ account_name: str | None = None,
+ account_code: str | None = None,
+ ) -> bool:
+ if not settings.email_enabled:
+ logger.warning("Email not sent — RESEND_API_KEY not configured")
+ return False
+
+ try:
+ import resend
+ from datetime import datetime, timezone
+
+ resend.api_key = settings.RESEND_API_KEY
+
+ date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
+ code_suffix = f" — {account_code}" if account_code else ""
+ subject = f"[ResolutionFlow Feedback] {feedback_type} — {date_str}{code_suffix}"
+
+ html = _render_feedback_html(
+ feedback_type=feedback_type,
+ message=message,
+ user_email=user_email,
+ account_name=account_name,
+ account_code=account_code,
+ )
+
+ resend.Emails.send(
+ {
+ "from": settings.FROM_EMAIL,
+ "to": [to_email],
+ "reply_to": reply_to_email,
+ "subject": subject,
+ "html": html,
+ }
+ )
+ logger.info("Feedback email sent from %s (type: %s)", user_email, feedback_type)
+ return True
+
+ except Exception:
+ logger.exception("Failed to send feedback email from %s", user_email)
+ return False
+```
+
+**Step 2: Add the HTML renderer**
+
+Add this function at the bottom of the file (after the other `_render_*` functions):
+
+```python
+def _render_feedback_html(
+ feedback_type: str,
+ message: str,
+ user_email: str,
+ account_name: str | None,
+ account_code: str | None,
+) -> str:
+ from datetime import datetime, timezone
+ import html
+
+ date_str = datetime.now(timezone.utc).strftime("%B %d, %Y")
+ safe_message = html.escape(message).replace("\n", "
")
+
+ account_line = ""
+ if account_name and account_code:
+ account_line = f"""
+
|
+
+ Account: {html.escape(account_name)} ({html.escape(account_code)})
+
+ |
"""
+
+ return f"""
+
+
+
+
+
+
+ ResolutionFlow Feedback
+ |
+ |
+
+ Type: {html.escape(feedback_type)}
+
+ |
+ |
+
+ From: {html.escape(user_email)}
+
+ |
+ {account_line}
+ |
+
+ Date: {date_str}
+
+ |
+ |
+
+ |
+ |
+
+ Reply directly to this email to respond to the user.
+
+ |
+
+ |
+
+"""
+```
+
+**Step 3: Commit**
+
+```bash
+git add backend/app/core/email.py
+git commit -m "feat: add send_feedback_email to EmailService"
+```
+
+---
+
+## Task 4: Backend Endpoint
+
+**Files:**
+- Create: `backend/app/api/endpoints/feedback.py`
+- Modify: `backend/app/api/router.py`
+
+**Step 1: Create the endpoint**
+
+Create `backend/app/api/endpoints/feedback.py`:
+
+```python
+import logging
+from typing import Annotated
+
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from sqlalchemy.ext.asyncio import AsyncSession
+from sqlalchemy import select
+
+from app.api.deps import get_current_active_user
+from app.core.config import settings
+from app.core.database import get_db
+from app.core.email import EmailService
+from app.core.rate_limit import limiter
+from app.models.user import User
+from app.models.account import Account
+from app.schemas.feedback import FeedbackSubmission, FeedbackResponse
+
+logger = logging.getLogger(__name__)
+
+router = APIRouter(tags=["feedback"])
+
+
+@router.post("/feedback", response_model=FeedbackResponse)
+@limiter.limit("1/minute")
+async def submit_feedback(
+ request: Request,
+ data: FeedbackSubmission,
+ current_user: Annotated[User, Depends(get_current_active_user)],
+ db: Annotated[AsyncSession, Depends(get_db)],
+):
+ """Submit user feedback via email."""
+ if not settings.FEEDBACK_EMAIL:
+ raise HTTPException(
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+ detail="Feedback submission is not configured",
+ )
+
+ # Get account info for the email
+ account_name = None
+ account_code = None
+ if current_user.account_id:
+ result = await db.execute(
+ select(Account).where(Account.id == current_user.account_id)
+ )
+ account = result.scalar_one_or_none()
+ if account:
+ account_name = account.name
+ account_code = account.display_code
+
+ sent = await EmailService.send_feedback_email(
+ to_email=settings.FEEDBACK_EMAIL,
+ reply_to_email=data.email,
+ feedback_type=data.feedback_type.value,
+ message=data.message,
+ user_email=current_user.email,
+ account_name=account_name,
+ account_code=account_code,
+ )
+
+ if not sent:
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail="Failed to send feedback. Please try again later.",
+ )
+
+ return FeedbackResponse(success=True, message="Thank you! Your feedback has been submitted.")
+```
+
+**Step 2: Register the router**
+
+In `backend/app/api/router.py`, add the import and include:
+
+Add to imports (line 6, after the existing imports):
+```python
+from app.api.endpoints import feedback
+```
+
+Add at the end of the router registrations:
+```python
+api_router.include_router(feedback.router)
+```
+
+**Step 3: Commit**
+
+```bash
+git add backend/app/api/endpoints/feedback.py backend/app/api/router.py
+git commit -m "feat: add POST /feedback endpoint"
+```
+
+---
+
+## Task 5: Backend Test
+
+**Files:**
+- Create: `backend/tests/test_feedback.py`
+
+**Step 1: Write the test**
+
+```python
+import pytest
+from unittest.mock import patch, AsyncMock
+
+
+@pytest.mark.asyncio
+async def test_submit_feedback(async_client, engineer_token, monkeypatch):
+ """Test successful feedback submission."""
+ monkeypatch.setenv("FEEDBACK_EMAIL", "support@test.com")
+ # Reload settings to pick up the env var
+ from app.core.config import Settings
+ test_settings = Settings()
+
+ with patch("app.api.endpoints.feedback.settings") as mock_settings, \
+ patch("app.api.endpoints.feedback.EmailService") as mock_email:
+ mock_settings.FEEDBACK_EMAIL = "support@test.com"
+ mock_email.send_feedback_email = AsyncMock(return_value=True)
+
+ response = await async_client.post(
+ "/api/v1/feedback",
+ json={
+ "email": "engineer@resolutionflow.example.com",
+ "feedback_type": "Bug Report",
+ "message": "Something is broken in the tree editor when I try to save.",
+ },
+ headers={"Authorization": f"Bearer {engineer_token}"},
+ )
+
+ assert response.status_code == 200
+ data = response.json()
+ assert data["success"] is True
+ assert "submitted" in data["message"].lower()
+
+
+@pytest.mark.asyncio
+async def test_submit_feedback_not_configured(async_client, engineer_token):
+ """Test 503 when FEEDBACK_EMAIL is not set."""
+ with patch("app.api.endpoints.feedback.settings") as mock_settings:
+ mock_settings.FEEDBACK_EMAIL = None
+
+ response = await async_client.post(
+ "/api/v1/feedback",
+ json={
+ "email": "engineer@resolutionflow.example.com",
+ "feedback_type": "General Feedback",
+ "message": "This is a general feedback message for testing.",
+ },
+ headers={"Authorization": f"Bearer {engineer_token}"},
+ )
+
+ assert response.status_code == 503
+
+
+@pytest.mark.asyncio
+async def test_submit_feedback_validation(async_client, engineer_token):
+ """Test validation — message too short."""
+ with patch("app.api.endpoints.feedback.settings") as mock_settings:
+ mock_settings.FEEDBACK_EMAIL = "support@test.com"
+
+ response = await async_client.post(
+ "/api/v1/feedback",
+ json={
+ "email": "engineer@resolutionflow.example.com",
+ "feedback_type": "Bug Report",
+ "message": "short",
+ },
+ headers={"Authorization": f"Bearer {engineer_token}"},
+ )
+
+ assert response.status_code == 422
+
+
+@pytest.mark.asyncio
+async def test_submit_feedback_invalid_type(async_client, engineer_token):
+ """Test validation — invalid feedback type."""
+ with patch("app.api.endpoints.feedback.settings") as mock_settings:
+ mock_settings.FEEDBACK_EMAIL = "support@test.com"
+
+ response = await async_client.post(
+ "/api/v1/feedback",
+ json={
+ "email": "engineer@resolutionflow.example.com",
+ "feedback_type": "Invalid Type",
+ "message": "This should fail because the type is invalid.",
+ },
+ headers={"Authorization": f"Bearer {engineer_token}"},
+ )
+
+ assert response.status_code == 422
+
+
+@pytest.mark.asyncio
+async def test_submit_feedback_requires_auth(async_client):
+ """Test that unauthenticated requests are rejected."""
+ response = await async_client.post(
+ "/api/v1/feedback",
+ json={
+ "email": "anon@example.com",
+ "feedback_type": "General Feedback",
+ "message": "This should fail because I'm not logged in.",
+ },
+ )
+ assert response.status_code == 401
+```
+
+**Step 2: Run the tests**
+
+```bash
+cd backend && pytest tests/test_feedback.py -v --override-ini="addopts="
+```
+
+Expected: All 5 tests pass. If any fail, debug and fix before proceeding.
+
+**Step 3: Commit**
+
+```bash
+git add backend/tests/test_feedback.py
+git commit -m "test: add feedback endpoint tests"
+```
+
+---
+
+## Task 6: Frontend API Client
+
+**Files:**
+- Create: `frontend/src/api/feedback.ts`
+- Modify: `frontend/src/api/index.ts`
+
+**Step 1: Create the API module**
+
+Create `frontend/src/api/feedback.ts`:
+
+```typescript
+import { apiClient } from './client'
+
+export interface FeedbackSubmission {
+ email: string
+ feedback_type: string
+ message: string
+}
+
+export interface FeedbackResponse {
+ success: boolean
+ message: string
+}
+
+export const feedbackApi = {
+ submit: async (data: FeedbackSubmission): Promise => {
+ const { data: response } = await apiClient.post('/feedback', data)
+ return response
+ },
+}
+
+export default feedbackApi
+```
+
+**Step 2: Export from index**
+
+In `frontend/src/api/index.ts`, add at the end:
+
+```typescript
+export { default as feedbackApi } from './feedback'
+```
+
+**Step 3: Commit**
+
+```bash
+git add frontend/src/api/feedback.ts frontend/src/api/index.ts
+git commit -m "feat: add feedback API client"
+```
+
+---
+
+## Task 7: Frontend Page
+
+**Files:**
+- Create: `frontend/src/pages/FeedbackPage.tsx`
+
+**Step 1: Create the page component**
+
+```tsx
+import { useState } from 'react'
+import { MessageSquareText, Send, CheckCircle2 } from 'lucide-react'
+import { useAuthStore } from '@/store/authStore'
+import { feedbackApi } from '@/api'
+import { cn } from '@/lib/utils'
+import { toast } from '@/lib/toast'
+
+const FEEDBACK_TYPES = [
+ 'Bug Report',
+ 'Feature Request',
+ 'Usability Issue',
+ 'Documentation',
+ 'General Feedback',
+] as const
+
+export function FeedbackPage() {
+ const user = useAuthStore(s => s.user)
+
+ const [email, setEmail] = useState(user?.email ?? '')
+ const [feedbackType, setFeedbackType] = useState('')
+ const [message, setMessage] = useState('')
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [submitted, setSubmitted] = useState(false)
+
+ const canSubmit = email.trim() && feedbackType && message.trim().length >= 10
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!canSubmit || isSubmitting) return
+
+ setIsSubmitting(true)
+ try {
+ const response = await feedbackApi.submit({
+ email: email.trim(),
+ feedback_type: feedbackType,
+ message: message.trim(),
+ })
+ if (response.success) {
+ setSubmitted(true)
+ setFeedbackType('')
+ setMessage('')
+ }
+ } catch (err: unknown) {
+ const error = err as { response?: { data?: { detail?: string } } }
+ toast.error(error.response?.data?.detail || 'Failed to submit feedback. Please try again.')
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ const handleNewFeedback = () => {
+ setSubmitted(false)
+ setEmail(user?.email ?? '')
+ }
+
+ return (
+
+ {/* Page header */}
+
+
+
+
Send Feedback
+
+
+ Help us improve ResolutionFlow. Report bugs, request features, or share your thoughts.
+
+
+
+
+ {submitted ? (
+
+
+
Thank you for your feedback!
+
+ We've received your submission and will review it shortly.
+
+
+
+ ) : (
+
+ )}
+
+
+ )
+}
+
+export default FeedbackPage
+```
+
+**Step 2: Commit**
+
+```bash
+git add frontend/src/pages/FeedbackPage.tsx
+git commit -m "feat: add FeedbackPage component"
+```
+
+---
+
+## Task 8: Router & Navigation
+
+**Files:**
+- Modify: `frontend/src/router.tsx`
+- Modify: `frontend/src/components/layout/Sidebar.tsx`
+- Modify: `frontend/src/pages/AccountSettingsPage.tsx`
+
+**Step 1: Add the route**
+
+In `frontend/src/router.tsx`:
+
+Add to the lazy imports section (after the `MyAnalyticsPage` import, around line 32):
+```typescript
+const FeedbackPage = lazy(() => import('@/pages/FeedbackPage'))
+```
+
+Add as a child of the `'/'` route, after the `analytics/me` route (around line 228, before the admin routes comment):
+```tsx
+ {
+ path: 'feedback',
+ element: (
+ }>
+
+
+ ),
+ },
+```
+
+**Step 2: Add the sidebar nav item**
+
+In `frontend/src/components/layout/Sidebar.tsx`:
+
+Add `MessageSquareText` to the lucide-react import (line 3):
+```typescript
+import { LayoutGrid, Box, PenLine, Clock, FileText, Bookmark, BarChart3, Users, Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText } from 'lucide-react'
+```
+
+In the collapsed sidebar section (around line 147, after the Analytics NavItem):
+```tsx
+
+```
+
+In the expanded sidebar section, add in the footer area (around line 202, before the Team NavItem):
+```tsx
+
+```
+
+**Step 3: Add the account settings link card**
+
+In `frontend/src/pages/AccountSettingsPage.tsx`:
+
+Add `MessageSquareText` to the lucide-react import.
+
+Add a link card after the last existing link card (after the Target Lists card), outside any permission guard:
+```tsx
+ {/* Feedback Link (all users) */}
+
+
+
+
+
Send Feedback
+
Report bugs, request features, or share your thoughts
+
+
+ →
+
+```
+
+**Step 4: Commit**
+
+```bash
+git add frontend/src/router.tsx frontend/src/components/layout/Sidebar.tsx frontend/src/pages/AccountSettingsPage.tsx
+git commit -m "feat: add feedback route, sidebar nav item, and account link card"
+```
+
+---
+
+## Task 9: Build Verification
+
+**Step 1: Run the frontend build**
+
+```bash
+cd frontend && npm run build
+```
+
+Expected: Build succeeds with no errors. TypeScript type-checking is stricter in the build than `tsc --noEmit`.
+
+**Step 2: Run the backend tests**
+
+```bash
+cd backend && pytest tests/test_feedback.py -v --override-ini="addopts="
+```
+
+Expected: All tests pass.
+
+**Step 3: Set FEEDBACK_EMAIL in .env for local testing**
+
+Add to `backend/.env`:
+```
+FEEDBACK_EMAIL=your-email@example.com
+```
+
+**Step 4: Manual smoke test**
+
+1. Start backend: `cd backend && uvicorn app.main:app --reload`
+2. Start frontend: `cd frontend && npm run dev`
+3. Log in as any test user
+4. Navigate to `/feedback` via sidebar
+5. Verify form loads with email pre-filled
+6. Submit feedback — verify success state
+7. Check email inbox for the formatted feedback email
+8. Verify the account settings page shows the "Send Feedback" link card
+
+---
+
+## Task 10: Final Commit & Cleanup
+
+**Step 1: Run full backend test suite**
+
+```bash
+cd backend && pytest --override-ini="addopts="
+```
+
+Expected: All tests pass, no regressions.
+
+**Step 2: Final frontend build check**
+
+```bash
+cd frontend && npm run build
+```
+
+Expected: Clean build.