Skip to Content

Invitations

Invitations allow you to add new members to your organization. This guide covers creating invitations, managing pending invites, and what happens when someone accepts an invitation.

Who Can Invite Members?

Only Owners and Admins can create invitations. Editors and Viewers cannot invite new members.

Invitation Workflow

The invitation process follows these steps:

1. Admin/Owner creates invitation 2. System generates unique invite link 3. Inviter shares link with invitee 4. Invitee clicks link and signs in 5. System verifies email matches 6. Member added to organization

Creating an Invitation

Step 1: Navigate to Members

Go to SettingsMembers in your organization.

Step 2: Click “Invite Member”

Click the Invite Member button at the top of the member list.

Step 3: Enter Invitation Details

Fill in the invitation form:

FieldDescriptionRequired
EmailThe email address of the person you’re invitingYes
RoleThe role they’ll have when they joinYes (defaults to Editor)

Step 4: Send the Invitation

Click Send Invitation to create the invite. You’ll see a success screen with:

  • Confirmation message
  • The invite link
  • A copy button

Copy the invite link and share it with the invitee via:

  • Email
  • Slack or Teams
  • Any other communication channel

Note: The system does not automatically send emails. You need to share the link manually.

Invitation links follow this pattern:

https://app.llmx.io/invite/{token}

The token is a unique, random identifier that:

  • Cannot be guessed
  • Expires after 7 days
  • Can only be used once
  • Is tied to the specified email address

Managing Pending Invitations

View and manage invitations that haven’t been accepted yet.

Viewing Pending Invitations

  1. Go to SettingsInvitations tab
  2. See all pending invitations with:
    • Invitee email
    • Assigned role
    • Date sent
    • Expiration status

Resending an Invitation

If an invitation is about to expire or the invitee lost the link:

  1. Find the invitation in the list
  2. Click the Resend button
  3. The expiration is extended by 7 days
  4. A new link is generated (the old link still works too)

Revoking an Invitation

To cancel an invitation before it’s accepted:

  1. Find the invitation in the list
  2. Click the Revoke button
  3. Confirm the cancellation

Revoked invitations:

  • Cannot be accepted anymore
  • Are marked as “Cancelled” in the list
  • Can be cleaned up by creating a new invitation if needed

Invitation Expiration

Invitations automatically expire after 7 days from creation.

What Happens When an Invitation Expires?

  • The invite link stops working
  • The invitee sees an “Invitation expired” message
  • The invitation is marked as “Expired” in your list

Handling Expired Invitations

  1. Go to SettingsInvitations
  2. Find the expired invitation
  3. Click Resend to extend the expiration, or
  4. Revoke it and create a new invitation

Accepting an Invitation

When someone receives your invitation link, here’s what they experience:

For Users with an Account

  1. Click the invitation link
  2. Sign in if not already authenticated
  3. See the invitation details (organization name, role)
  4. Click Accept Invitation
  5. Get redirected to the organization workspace

For New Users

  1. Click the invitation link
  2. Create an account using the same email address
  3. See the invitation details
  4. Click Accept Invitation
  5. Get redirected to the organization workspace

Email Verification

The invitation email must match the user’s authenticated email address.

Why?

  • Prevents invitation hijacking
  • Ensures the right person joins
  • Maintains organizational security

What if emails don’t match?

  • The user sees an error message
  • They cannot accept the invitation
  • They need to sign in with the correct email, or
  • You need to create a new invitation with their actual email

Invitation Statuses

Invitations can have one of four statuses:

StatusDescription
PendingWaiting to be accepted (active and valid)
AcceptedThe invitee has joined the organization
ExpiredThe 7-day window has passed
CancelledAn admin revoked the invitation

Best Practices

Use Accurate Email Addresses

Always verify the invitee’s email address before sending. The email matching requirement prevents accidental acceptance by the wrong person.

Set Appropriate Roles

Consider the invitee’s responsibilities when choosing their role:

  • Viewer for stakeholders who only need to review
  • Editor for team members who create content
  • Admin for managers who help run the team
  • Owner sparingly, only for key personnel

Clean Up Expired Invitations

Periodically review your invitations list and clean up expired or cancelled invitations by revoking and recreating as needed.

Communicate Expiration

When sharing invitation links, let invitees know they expire in 7 days. This encourages prompt action.


For Developers

Invitation Model

class InvitationStatus(str, Enum): PENDING = "pending" ACCEPTED = "accepted" EXPIRED = "expired" CANCELLED = "cancelled" class InvitationResponse(BaseModel): id: str email: str role: OrgRole status: InvitationStatus token: str # UUID for invite link org_id: str org_name: str org_slug: str invited_by: str # User ID invited_by_name: str created_at: datetime expires_at: datetime accepted_at: Optional[datetime] accepted_by: Optional[str]

Token Generation

Invitation tokens are UUIDs (v4), providing:

  • 128 bits of randomness
  • Practically impossible to guess
  • No sequential patterns
import uuid token = str(uuid.uuid4()) # Example: "550e8400-e29b-41d4-a716-446655440000"

Expiration Logic

from datetime import datetime, timedelta INVITATION_VALIDITY_DAYS = 7 def create_invitation(email: str, role: OrgRole) -> Invitation: return Invitation( token=str(uuid.uuid4()), email=email, role=role, status=InvitationStatus.PENDING, created_at=datetime.utcnow(), expires_at=datetime.utcnow() + timedelta(days=INVITATION_VALIDITY_DAYS) ) def is_expired(invitation: Invitation) -> bool: return datetime.utcnow() > invitation.expires_at def resend_invitation(invitation: Invitation) -> Invitation: invitation.expires_at = datetime.utcnow() + timedelta(days=INVITATION_VALIDITY_DAYS) return invitation

Email Matching

Acceptance requires case-insensitive email matching:

def accept_invitation(token: str, user_email: str): invitation = await get_invitation_by_token(token) if invitation.email.lower() != user_email.lower(): raise HTTPException(403, "Email does not match invitation") if invitation.status != InvitationStatus.PENDING: raise HTTPException(400, "Invitation is no longer valid") if is_expired(invitation): raise HTTPException(400, "Invitation has expired") # Add user to organization await add_member(invitation.org_id, user_id, invitation.role) # Update invitation status invitation.status = InvitationStatus.ACCEPTED invitation.accepted_at = datetime.utcnow() invitation.accepted_by = user_id

Collection Group Query

Finding invitations by token uses a Firestore collection group query across all organizations:

async def get_invitation_by_token(token: str) -> Invitation: query = db.collection_group("invitations").where("token", "==", token) results = await query.get() if not results: raise HTTPException(404, "Invitation not found") return results[0].to_dict()
Last updated on