# Survey Invite Tracking — Design

> **Date:** 2026-03-04
> **Status:** Approved

## Goal

Add invite tracking to the FlowPilot survey so Michael can create personalized links, optionally email them, and see who has/hasn't responded. Each invite token is single-use — one submission per token.

## Data Model

### New table: `survey_invites`

| Column | Type | Notes |
|--------|------|-------|
| `id` | UUID PK | |
| `token` | VARCHAR(32) UNIQUE | Random URL-safe token |
| `recipient_name` | VARCHAR(255) NOT NULL | Who it's for |
| `recipient_email` | VARCHAR(255) NULL | Only if emailing |
| `status` | VARCHAR(20) DEFAULT 'pending' | `pending` or `completed` |
| `email_sent` | BOOLEAN DEFAULT false | Whether Resend email was sent |
| `created_at` | TIMESTAMPTZ NOT NULL | |
| `completed_at` | TIMESTAMPTZ NULL | Set on submission |

### Modified table: `survey_responses`

Add `invite_id` UUID FK nullable → `survey_invites.id`. Responses from tokenless `/survey` have `invite_id = NULL`.

## API Endpoints

### Public (no auth)

- `GET /api/v1/survey/invite/{token}` — Returns invite status (`{ name, status }`). If `completed`, frontend shows "already submitted" screen. Returns 404 for invalid tokens.
- `POST /api/v1/survey/submit` — Modified: accepts optional `token` field. If token provided, validates it's `pending`, links the response, and marks invite as `completed`. Returns 409 if token already used.

### Admin (super_admin auth)

- `POST /api/v1/admin/survey-invites` — Create invite. Body: `{ recipient_name, recipient_email?, send_email? }`. Generates token, optionally sends email. Returns the invite with the full survey URL.
- `GET /api/v1/admin/survey-invites` — List all invites with status.

## Frontend

### Survey page changes (`/survey`)

- On load, reads `?t=<token>` from URL params
- If token present: calls `GET /survey/invite/{token}`
  - If `completed` → show "already submitted" screen
  - If `pending` → show survey, include token in submission payload
  - If 404 → show survey without token (treat as open link)
- If no token: show survey as-is (open access)

### Admin page (`/admin/survey-invites`)

**Top section: Create Invite**
- Name input (required) + Email input (optional)
- "Generate Link" button → creates invite, shows URL with copy button
- "Send Email" button → creates invite with `send_email: true`, shows confirmation toast
- "Send Email" only enabled when email field is filled

**Bottom section: Invite Table**
- Columns: Name, Email, Status badge (pending amber / completed green), Sent (email icon or dash), Created date, Completed date
- Sorted by created_at descending

## Email Template

Uses existing `EmailService` + Resend pattern. Dark-themed email matching Slate & Ice aesthetic:
- Subject: "FlowPilot Survey — Your expertise matters"
- Body: Brief intro, CTA button linking to `/survey?t=<token>`, ~5 minutes note
- From: existing `FROM_EMAIL` config

## Constraints

- No token expiration
- No reminder/resend (keep it simple)
- Tokenless survey still works for open sharing
- One submission per invite token (enforced backend + frontend)
