Milestone 9: Invites and team
Purpose: Admins can invite users by email; invitee accepts (new user sets password, existing user gets membership); team settings list members and pending invites; audit log written and viewable.
Exit state: Invite flow (create + email); accept flow (new vs existing user); Settings → Team (members, invites, resend/revoke); Settings → Audit (table, filters).
Spec reference: §2.5 Invite users, §2.9 Audit log, §5.14 Invitation, §6.10 invite page, team settings, §10.4 Users & team members.
Prerequisites: M08 (Notifications — invite email uses Postmark).
9.1 Invitations
Tasks
-
Create invite
- tRPC: input email, role (admin/agent/viewer). Validate email format; check no existing membership for (tenant_id, email); check no pending invite for (tenant_id, email) or allow resend. Generate token (random, unique); set expires_at (e.g. now + 7 days). Insert invitations row; send invite email (Postmark, template from M08) with accept_url =
APP_URL/invite/{token}. Section 2.5. - Audit: write audit_logs row action user.invited, resource invitation.
- tRPC: input email, role (admin/agent/viewer). Validate email format; check no existing membership for (tenant_id, email); check no pending invite for (tenant_id, email) or allow resend. Generate token (random, unique); set expires_at (e.g. now + 7 days). Insert invitations row; send invite email (Postmark, template from M08) with accept_url =
-
Invite email
- Template: org_name, inviter_name, role, accept_url, expires_at. Section 2.10b.
-
Accept flow — route
/invite/[token]- GET: validate token (exists, not expired, accepted_at null). Load invitation (tenant_id, email, role). If user not logged in: show "Sign in" or "Create account" with link that preserves token (e.g. /invite/[token] after login). If logged in: show "Accept" (add membership and redirect).
- POST accept (or link click): validate token; if existing user (email matches): create tenant_memberships row; set invitation accepted_at; redirect to /app/[slug]/tickets (invited tenant slug). If new user: redirect to set-password page (same token or new token); after password set create user (password_hash set), create membership, set accepted_at; create session; redirect to /app/[slug]/tickets. Section 2.5, 6.10.
- Set-password page for new user: use invite token to identify invitation; form new password (12 char min); on submit create user, membership, accept invite, session; redirect.
-
Revoke invite
- tRPC: set invitation accepted_at or delete; optionally send "invite revoked" (optional). Audit: user.invite_revoked.
Acceptance criteria
| Criterion | Status |
|---|---|
| Admin can invite by email + role; invitee receives email with link. | Pending |
| Existing user: accept adds membership and redirects to tenant. | Pending |
| New user: accept leads to set password; after password, user created and membership added; redirect to tenant. | Pending |
| Expired or invalid token shows clear message; revoke prevents accept. | Pending |
| Audit log has user.invited and user.invite_revoked. | Pending |
9.2 Settings → Team
Tasks
-
Members list
- tRPC: list tenant_memberships for tenant with user (name, email, avatar_url). Display: name, email, role (select to change), Remove button. Role change: tRPC update membership role; audit user.role_changed. Remove: delete membership; optional reassign open tickets (Section 10.4); audit user.removed. Confirm Remove with AlertDialog.
-
Invite member UI
- "Invite member" button; modal: email input, role select; submit calls create invite (9.1). Show success "Invite sent."
-
Pending invites
- List invitations where accepted_at null and expires_at > now. Columns: email, role, expires_at, invited_by (optional). Actions: Resend (new token, resend email), Revoke. Section 6.10.
Acceptance criteria
| Criterion | Status |
|---|---|
| Team table shows all members; Admin can change role and remove member; Remove prompts for reassign if needed. | Pending |
| Invite modal sends invite; pending list shows with resend/revoke. | Pending |
| Audit entries for role change and remove. | Pending |
| Team list and pending invites reflected in real time when another admin adds/removes member or invite (SSE: e.g. team.updated or notification.new; Section 4.11). | Pending |
9.3 Audit log
Tasks
-
Write audit events
- Ensure all actions in Section 2.9 table write to audit_logs: tenant.updated, inbox., ticket.created (manual only), ticket.deleted, ticket.status_changed, ticket.assigned, ticket.moved, message.deleted, user.invited, user.invite_revoked, user.role_changed, user.removed, sla_policy., api_key., webhook., subscription.changed, tenant.deleted. Use user_id from context (null for system). resource_type, resource_id, metadata jsonb as needed.
-
Settings → Audit
- tRPC: list audit_logs for tenant; filters: date range, actor (user_id), action type. Sort by created_at desc. Table: timestamp, actor (name or "System"), action (human-readable), resource, optional metadata expandable. Section 6.10. Retention: do not delete in M09; pruning cron in M16 or document retention per plan (Section 2.9).
-
Retention
- Document or implement: Starter 30d, Growth 90d, Business 1y, Enterprise custom. Weekly cron to delete older rows (can be M10/M16).
Acceptance criteria
| Criterion | Status |
|---|---|
| Key actions (invite, role change, remove, inbox connect, ticket create/delete, etc.) write to audit_logs. | Pending |
| Settings → Audit shows entries; filter by date and actor works. | Pending |
| Retention policy documented or cron in place. | Pending |
Milestone 9 sign-off
| Criterion | Status |
|---|---|
| All tasks in 9.1–9.3 complete. | Pending |
| All acceptance criteria met. | Pending |
| Invite and accept flows work for new and existing users; team and audit visible to Admin. | Pending |
| E2E: Invite send/accept and team/audit UI (see INDEX — Testing strategy). | Pending |
| Ready for M10 (Billing and trial). | Pending |