Team Invitations

In B2B mode, workspace owners and admins can invite new members via email.

Invitation Flow
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   Admin sends    │     │  Invitee gets    │     │  Invitee clicks  │
│   invitation     │────▶│  email with      │────▶│  accept link     │
│   (email + role) │     │  magic link      │     │                  │
└──────────────────┘     └──────────────────┘     └──────────────────┘
                                                            │
                                                            ▼
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   Member added   │     │  Membership      │     │  Token validated │
│   to workspace   │◀────│  created with    │◀────│  (7-day expiry)  │
│                  │     │  assigned role   │     │                  │
└──────────────────┘     └──────────────────┘     └──────────────────┘
Sending Invitations

Admins and owners can invite new members from the org dashboard's Members page. The invitation form collects the invitee's email and assigns a role. The system generates a unique token, stores the invitation in the invitations table with a 7-day expiry, and sends the invite email through the active email provider with a locale-appropriate template.

Invitation API

Invitations are created by a server action from the workspace members page (not a public REST endpoint). The HTTP API surface is POST /api/invitations/accept (accept an invite), POST /api/invitations/resend (owner/admin re-send), and POST /api/invitations/revoke (owner/admin revoke). Each path validates the user's permission, checks for duplicate or pending invitations where relevant, and enforces the workspace's member limit based on the current plan.

Accepting Invitations

When a user clicks the invitation link, they are taken to /invite/accept which validates the token and expiry. If the user is already authenticated, the membership is created immediately. If not, they are prompted to log in or create an account first, then redirected back to complete the acceptance.

Invitation Database Schema

The invitations table stores the account ID, invited email, role slug, invitation token (unique), expiry date, status (pending / accepted / expired / revoked), and who sent the invitation. RLS scopes reads to the invitation's account; writes go through server actions / SECURITY DEFINER paths, not user-level INSERT policies.

Managing Pending Invitations

The Members page in the org dashboard shows all pending invitations alongside current members. Admins can resend an expired invitation or cancel a pending one. The pending-invitations.tsx component displays the invitee's email, assigned role, expiry status, and action buttons.

Invitation Email Templates

Invitation emails are rendered by a single shared builder — buildInvitationEmail() in lib/email/templates.ts — wrapped in the universal email chrome from lib/email/layout.ts. Locale strings come from the email.invitation.* i18n keys (FR/EN), so there is no separate workspace-invitation-fr / -en file pair.

Builder Source Parameters
buildInvitationEmail lib/email/templates.ts workspaceName, role, inviteLink, inviterName, appName, locale
Security Notes:
  • Invitation tokens are single-use and expire after 7 days
  • Only owners and admins can send invitations
  • Users cannot invite themselves
  • Duplicate invitations to the same email are prevented

B2B Subscription Flow

Invitations sit on top of an already-existing workspace, so the workspace bootstrap, pending-checkout cookie, Stripe round-trip, and onboarding gate live in the parent page. See Multi-Tenancy Overview for the full B2B subscription flow (five auto-bootstrap call sites, single onboarding gate, workspace-manager redirect rule).

Invited User Flow

When an invited user accepts an invitation:

  1. Click invitation link → Accept invitation page
  2. API-based acceptance via /api/invitations/accept
  3. Pending invitation revocation via /api/invitations/revoke with authenticated CSRF-protected server validation
  4. Redirect to success page → Onboarding (if needed)
  5. Subscription check: If workspace has active subscription, redirect via the workspace-manager rule (isWorkspaceManager(user.id)) — /org-dashboard for owners/admins, else /private-dashboard (not pricing)
  6. Dashboard prioritizes workspace account for data display

Organization Member Limits

Organizations can have configurable member limits:

Feature Description
max_members Configurable per organization (null = unlimited)
Enforcement Limit checked on member invitation
Admin Update Can be updated via admin dashboard
Visual Indicator Warning shown when limit is reached