Skip to main content

Inviting Members

Add team members to your organization with email invitations and role assignments.

Overview

LinkForty's team invitation system allows you to:

  • Send email invitations to colleagues
  • Assign roles before members join
  • Track pending, accepted, and declined invitations
  • Set automatic expiration (7 days)
  • Manage invitations through dashboard or API

Key Concepts:

  • Invitation - Email invite with unique token and assigned role
  • Pending - Invitation sent but not yet responded to
  • Accepted - Member joined the organization
  • Declined - Invitation rejected by recipient
  • Expired - Invitation passed 7-day validity period

Sending Invitations

Via Dashboard

  1. Go to SettingsTeam
  2. Click "Invite Member"
  3. Enter email address
  4. Select role (Admin, Member, or Viewer)
  5. Click "Send Invitation"

The recipient receives an email with an invitation link.

Via API

curl -X POST https://api.linkforty.com/api/organizations/org_abc123/invitations \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "sarah@example.com",
"role": "member"
}'

Response:

{
"id": "inv_xyz789",
"email": "sarah@example.com",
"role": "member",
"token": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"expires_at": "2024-03-22T14:30:00Z",
"invitation_url": "https://app.linkforty.com/invitations/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}

Requires: Admin or Owner role

Note: Cannot assign "Owner" role via invitation. Only existing Owners can transfer ownership.

Invitation Flow

1. Admin/Owner sends invitation

2. Email sent to recipient

3. Recipient clicks link

4. Logs in or creates account

5. Accepts or declines invitation

6. [If accepted] Added to organization

Managing Invitations

Viewing Pending Invitations

Dashboard:

  1. Go to SettingsTeam
  2. Click "Pending Invitations" tab
  3. See all sent invitations with status

API:

curl https://api.linkforty.com/api/organizations/org_abc123/invitations \
-H "Authorization: Bearer $LINKFORTY_API_KEY"

Response:

[
{
"id": "inv_123",
"email": "sarah@example.com",
"role": "member",
"status": "pending",
"expires_at": "2024-03-22T14:30:00Z",
"created_at": "2024-03-15T14:30:00Z",
"invited_by_name": "John Doe",
"invited_by_email": "john@example.com"
},
{
"id": "inv_456",
"email": "mike@example.com",
"role": "admin",
"status": "accepted",
"accepted_at": "2024-03-16T10:15:00Z",
"expires_at": "2024-03-22T09:00:00Z",
"created_at": "2024-03-15T09:00:00Z",
"invited_by_name": "John Doe",
"invited_by_email": "john@example.com"
}
]

Canceling Invitations

Dashboard:

  1. Go to SettingsTeam"Pending Invitations"
  2. Click "Cancel" next to invitation
  3. Confirm cancellation

API:

curl -X DELETE https://api.linkforty.com/api/organizations/org_abc123/invitations/inv_xyz789 \
-H "Authorization: Bearer $LINKFORTY_API_KEY"

Response:

{
"message": "Invitation cancelled successfully"
}

Requires: Admin or Owner role

Why Cancel:

  • Sent to wrong email
  • Role assignment incorrect (need to resend with different role)
  • Person no longer joining team

Accepting Invitations

Via Dashboard

  1. Click invitation link from email
  2. Log in (or create account if new user)
  3. Review organization details
  4. Click "Accept Invitation"
  5. Redirected to organization dashboard

Via API

Step 1: Get invitation details

curl https://api.linkforty.com/api/invitations/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Response:

{
"id": "inv_123",
"email": "sarah@example.com",
"role": "member",
"status": "pending",
"expires_at": "2024-03-22T14:30:00Z",
"created_at": "2024-03-15T14:30:00Z",
"organization": {
"id": "org_abc123",
"name": "Acme Marketing Team",
"slug": "acme-marketing-team-x7y9z2"
},
"invited_by": {
"name": "John Doe",
"email": "john@example.com"
}
}

Step 2: Accept invitation

curl -X POST https://api.linkforty.com/api/invitations/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6/accept \
-H "Authorization: Bearer $LINKFORTY_API_KEY"

Response:

{
"message": "Invitation accepted successfully",
"organization": {
"id": "org_abc123",
"name": "Acme Marketing Team",
"slug": "acme-marketing-team-x7y9z2"
}
}

Note: Requires authentication. Email must match invitation email.

Declining Invitations

Via Dashboard

  1. Click invitation link from email
  2. Log in
  3. Review organization details
  4. Click "Decline Invitation"

Via API

curl -X POST https://api.linkforty.com/api/invitations/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6/decline \
-H "Authorization: Bearer $LINKFORTY_API_KEY"

Response:

{
"message": "Invitation declined"
}

Invitation Statuses

StatusDescriptionCan Resend?
pendingSent, awaiting responseNo (must cancel first)
acceptedMember joined organizationN/A
declinedRecipient rejected invitationYes
expired7 days passed without responseYes

Common Scenarios

1. Inviting Multiple Team Members

Bulk invite via API:

const emails = [
{ email: 'sarah@example.com', role: 'member' },
{ email: 'mike@example.com', role: 'admin' },
{ email: 'emma@example.com', role: 'viewer' },
];

for (const invite of emails) {
await fetch(`https://api.linkforty.com/api/organizations/org_abc123/invitations`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(invite),
});

// Wait 100ms between requests to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 100));
}

2. Resending Expired Invitations

Check for expired invitations:

async function resendExpiredInvitations(orgId: string) {
const response = await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const invitations = await response.json();

// Find expired invitations
const expired = invitations.filter(inv => inv.status === 'expired');

// Resend each one
for (const inv of expired) {
// Cancel old invitation
await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations/${inv.id}`,
{
method: 'DELETE',
headers: { 'Authorization': `Bearer ${API_KEY}` },
}
);

// Send new invitation
await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: inv.email,
role: inv.role,
}),
}
);
}
}

3. Client Stakeholder Access

Invite client with viewer role:

curl -X POST https://api.linkforty.com/api/organizations/org_abc123/invitations \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "client@nike.com",
"role": "viewer"
}'

Viewer role gives read-only access - perfect for clients monitoring campaign performance.

4. Agency Onboarding New Team Member

Workflow:

async function onboardNewMember(orgId: string, email: string) {
// 1. Send invitation
const inviteResponse = await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
role: 'member',
}),
}
);

const invitation = await inviteResponse.json();

console.log(`✅ Invitation sent to ${email}`);
console.log(`📧 Invitation link: ${invitation.invitation_url}`);
console.log(`⏰ Expires: ${new Date(invitation.expires_at).toLocaleDateString()}`);

return invitation;
}

TypeScript Examples

1. Check Invitation Status

interface Invitation {
id: string;
email: string;
role: 'admin' | 'member' | 'viewer';
status: 'pending' | 'accepted' | 'declined' | 'expired';
expires_at: string;
created_at: string;
invited_by_name: string;
invited_by_email: string;
accepted_at?: string;
}

async function getInvitationStatus(
orgId: string,
email: string
): Promise<Invitation | null> {
const response = await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const invitations: Invitation[] = await response.json();

return invitations.find(inv => inv.email === email) || null;
}

// Usage
const status = await getInvitationStatus('org_abc123', 'sarah@example.com');

if (status) {
console.log(`Status: ${status.status}`);
console.log(`Role: ${status.role}`);
console.log(`Expires: ${new Date(status.expires_at).toLocaleDateString()}`);
} else {
console.log('No invitation found');
}

2. Track Invitation Acceptance Rate

async function getInvitationMetrics(orgId: string) {
const response = await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const invitations: Invitation[] = await response.json();

const total = invitations.length;
const accepted = invitations.filter(inv => inv.status === 'accepted').length;
const pending = invitations.filter(inv => inv.status === 'pending').length;
const declined = invitations.filter(inv => inv.status === 'declined').length;
const expired = invitations.filter(inv => inv.status === 'expired').length;

return {
total,
accepted,
pending,
declined,
expired,
acceptanceRate: total > 0 ? ((accepted / total) * 100).toFixed(1) + '%' : '0%',
};
}

// Usage
const metrics = await getInvitationMetrics('org_abc123');
console.table(metrics);

3. Auto-Cleanup Expired Invitations

async function cleanupExpiredInvitations(orgId: string) {
const response = await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

const invitations: Invitation[] = await response.json();

// Find invitations older than 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

const oldInvitations = invitations.filter(inv => {
const createdAt = new Date(inv.created_at);
return createdAt < thirtyDaysAgo &&
(inv.status === 'expired' || inv.status === 'declined');
});

// Delete old invitations
for (const inv of oldInvitations) {
await fetch(
`https://api.linkforty.com/api/organizations/${orgId}/invitations/${inv.id}`,
{
method: 'DELETE',
headers: { 'Authorization': `Bearer ${API_KEY}` },
}
);
}

return {
deleted: oldInvitations.length,
emails: oldInvitations.map(inv => inv.email),
};
}

Invitation Email

Recipients receive an email with:

Subject: You've been invited to join [Organization Name] on LinkForty

Content:

  • Inviter's name and organization name
  • Assigned role (Admin, Member, or Viewer)
  • Call-to-action button with invitation link
  • Expiration notice (7 days)

Email Template Variables:

  • {organizationName} - Organization being joined
  • {inviterName} - Person who sent invitation
  • {role} - Assigned role
  • {invitationUrl} - Unique acceptance link

Limits and Restrictions

Member Limits by Plan

PlanMax MembersMax Pending Invitations
Free10 (upgr. required)
Pro2510
EnterpriseUnlimitedUnlimited

Invitation Rules

  • ✅ Can invite to roles: Admin, Member, Viewer
  • ❌ Cannot invite as Owner (must transfer ownership after joining)
  • ✅ Can invite email not yet registered
  • ❌ Cannot invite existing organization member
  • ✅ Can have multiple pending invitations
  • ❌ Cannot send duplicate invitations to same email
  • ✅ Invitations expire after 7 days
  • ✅ Can resend after cancellation or expiration

Troubleshooting

"User is already a member"

Cause: Email address is already in the organization

Fix:

  1. Check team members list
  2. If wrong role, change role instead of re-inviting
  3. If duplicate email, verify spelling

"Invitation already sent"

Cause: Pending invitation exists for this email

Fix:

  1. Check pending invitations
  2. Cancel existing invitation first
  3. Send new invitation

"Invitation has expired"

Cause: More than 7 days passed since invitation sent

Fix:

  1. Admin cancels expired invitation
  2. Admin sends new invitation
  3. Recipient accepts within 7 days

"Member limit reached"

Cause: Organization hit plan's member limit

Fix:

  1. Check usage: GET /api/organizations/:id/usage
  2. Remove inactive members
  3. Upgrade plan

"Email doesn't match"

Cause: Logged-in user email differs from invitation email

Fix:

  1. Log out
  2. Create account with invited email
  3. Accept invitation

Best Practices

1. Use Descriptive Role Assignments

✅ Good:

  • Assign Admin to team leads who manage settings
  • Assign Member to content creators and marketers
  • Assign Viewer to clients and stakeholders

❌ Bad:

  • Making everyone Admin "just in case"
  • Assigning Member when Viewer is sufficient

2. Track Invitation Status

// Weekly invitation audit
async function auditInvitations(orgId: string) {
const metrics = await getInvitationMetrics(orgId);

if (metrics.pending > 5) {
console.warn(`⚠️ ${metrics.pending} pending invitations - follow up?`);
}

if (parseInt(metrics.acceptanceRate) < 50) {
console.warn('⚠️ Low acceptance rate - check email deliverability');
}

return metrics;
}

3. Clean Up Old Invitations

// Monthly cleanup
setInterval(async () => {
await cleanupExpiredInvitations('org_abc123');
}, 30 * 24 * 60 * 60 * 1000); // 30 days

4. Provide Context in Custom Emails

If using API to send custom emails, include:

  • Why they're being invited
  • What they'll be working on
  • Who they'll be collaborating with
  • What access they'll have (based on role)

Security

Email Verification

  • Token-based - Unique 32-character token per invitation
  • Email matching - Must log in with invited email
  • Expiration - 7-day validity period
  • Single-use - Token invalidated after acceptance/decline

Access Control

  • ✅ Only Admins and Owners can send invitations
  • ✅ Only invited email can accept invitation
  • ✅ Tokens are cryptographically secure (nanoid)
  • ✅ Invitation details hidden after expiration

Next Steps

  1. Invite your first team member
  2. Assign appropriate roles based on responsibilities
  3. Track invitation acceptance
  4. Set up regular invitation audits