Inviting Users

The ContactsManager SDK provides a powerful invite system that enables controlled app growth, user acquisition tracking, and viral loop implementation. The invite service supports multiple distribution methods and provides detailed analytics for measuring growth effectiveness.

Accessing the Invite Service

The invite service is accessed through the ContactsManager shared instance:

import ContactsManager

let cm = ContactsService.shared
let inviteService = cm.invites

// Access invite properties and methods
let inviteCode = await inviteService.code
let inviteLink = await inviteService.link
let remainingInvites = await inviteService.remaining()

Invite Code Management

Getting Current Invite Code

Retrieve the user’s current invite code, or create one if none exists:

let inviteCode = await cm.invites.code

if let code = inviteCode {
    print("Your invite code: \(code)")
    // Display code to user for sharing
} else {
    print("Unable to get invite code")
}

Retrieve the full invite URL for web-based sharing:

let inviteLink = await cm.invites.link

if let link = inviteLink {
    print("Share this link: \(link)")
    // Use link for social sharing, email, etc.
} else {
    print("Unable to get invite link")
}

Creating New Invites

Manually create a new invite (useful for generating fresh codes):

let newInvite = await cm.invites.createInvite()

if let invite = newInvite {
    print("New invite code: \(invite.inviteCode)")
    print("New invite URL: \(invite.inviteUrl)")
    print("Expires at: \(invite.expiresAt)")
} else {
    print("Failed to create new invite")
}

Invite Validation and Redemption

Validating Invite Codes

Validate an invite code before allowing redemption:

let validation = await cm.invites.validateCode(code: "ABC123")

if let result = validation {
    if result.valid {
        print("Valid invite from: \(result.inviter?.displayName ?? "Unknown")")
        print("Organization: \(result.organizationName ?? "N/A")")
        print("Remaining invites: \(result.remainingInvites ?? 0)")
    } else {
        print("Invalid invite: \(result.message)")
    }
} else {
    print("Unable to validate invite code")
}

Redeeming Invites

Redeem an invite code for a new user:

let success = await cm.invites.redeemInvite(
    code: "ABC123", 
    for: "new_user_id_123"
)

if success {
    print("Invite redeemed successfully")
    // Proceed with user onboarding
} else {
    print("Failed to redeem invite")
}

Getting Invite Details (Public)

Get detailed invite information for landing pages (doesn’t require authentication):

let details = await cm.invites.getInviteDetails(for: "ABC123")

if let inviteDetails = details {
    if inviteDetails.valid {
        print("Invited by: \(inviteDetails.inviter?.displayName ?? "Someone")")
        print("Join \(inviteDetails.organizationName ?? "this app")")
    } else {
        print("Invite expired or invalid")
    }
}

Invite Analytics and Tracking

Checking Remaining Invites

Get the number of invites the current user has left:

let remaining = await cm.invites.remaining()
print("You have \(remaining) invites remaining")

// Use this to show invite limits in UI
if remaining > 0 {
    // Show invite button
} else {
    // Show "no invites left" message
}

Getting Invited Users

Retrieve the list of users invited by the current user:

let invitedUsers = await cm.invites.invited()

print("You've invited \(invitedUsers.count) users:")
for user in invitedUsers {
    print("- \(user.displayName ?? "Unknown")")
}

// Use for showing invite success and building social proof

Finding Who Invited You

Get information about who invited the current user:

let inviter = await cm.invites.invitedBy()

if let whoInvitedMe = inviter {
    print("You were invited by: \(whoInvitedMe.displayName ?? "Unknown")")
    // Show connection to inviter in onboarding
    // Enable features that connect inviter and invitee
} else {
    print("You joined without an invite")
}

Bulk Email Invitations

Checking Email Invite Availability

Verify if bulk email invites are available for your organization:

let emailAvailable = await cm.invites.isBulkInviteEmailAvailable()

if emailAvailable {
    // Show email invite options
    print("Email invites are available")
} else {
    // Show setup instructions or alternative invite methods
    print("Email invites require domain verification")
}

Sending Bulk Email Invites

Send invite emails to multiple contacts at once:

// Get contacts to invite (from recommendations or user selection)
let contactsToInvite = await cm.people.fetch(
    matching: [.suggestedInvite], 
    limit: 10
)

let emailBody = """
Hi there!

I've been using this amazing app and thought you'd love it too. 
It's helped me stay organized and connect with friends.

Join me and let's explore it together!

Best,
[Your name]
"""

let result = await cm.invites.sendBulkInviteEmails(
    contacts: contactsToInvite,
    emailBody: emailBody
)

if let response = result {
    if response.success {
        print("Sent \(response.emailsSent) invites successfully")
        if !response.failedEmails.isEmpty {
            print("Failed to send to: \(response.failedEmails.joined(separator: ", "))")
        }
    } else {
        print("Email invite failed: \(response.message)")
    }
}

Building Invite Flows

Basic Invite Sharing

Create a simple invite sharing interface:

struct InviteView: View {
    @State private var inviteCode: String?
    @State private var inviteLink: String?
    @State private var remainingInvites: Int = 0
    @State private var isLoading = true
    
    var body: some View {
        VStack(spacing: 20) {
            if isLoading {
                ProgressView("Loading invite info...")
            } else {
                VStack(spacing: 16) {
                    Text("Invite Friends")
                        .font(.title2)
                        .fontWeight(.bold)
                    
                    Text("You have \(remainingInvites) invites remaining")
                        .foregroundColor(.secondary)
                    
                    if let code = inviteCode {
                        VStack {
                            Text("Your Invite Code")
                                .font(.headline)
                            Text(code)
                                .font(.title)
                                .fontWeight(.bold)
                                .padding()
                                .background(Color.gray.opacity(0.1))
                                .cornerRadius(8)
                        }
                    }
                    
                    if let link = inviteLink {
                        ShareLink(
                            item: URL(string: link)!,
                            message: Text("Join me on this amazing app!")
                        ) {
                            Label("Share Invite Link", systemImage: "square.and.arrow.up")
                                .frame(maxWidth: .infinity)
                                .padding()
                                .background(Color.blue)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                        }
                    }
                }
            }
        }
        .padding()
        .onAppear {
            loadInviteInfo()
        }
    }
    
    private func loadInviteInfo() async {
        let cm = ContactsService.shared
        
        async let code = cm.invites.code
        async let link = cm.invites.link
        async let remaining = cm.invites.remaining()
        
        let (inviteCode, inviteLink, remainingCount) = await (code, link, remaining)
        
        DispatchQueue.main.async {
            self.inviteCode = inviteCode
            self.inviteLink = inviteLink
            self.remainingInvites = remainingCount
            self.isLoading = false
        }
    }
}

Invite Redemption Flow

Handle invite code redemption during onboarding:

struct InviteRedemptionView: View {
    @State private var inviteCode = ""
    @State private var isValidating = false
    @State private var validationResult: ValidateInviteCodeResponse?
    @State private var showError = false
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Enter Invite Code")
                .font(.title2)
                .fontWeight(.bold)
            
            TextField("Invite Code", text: $inviteCode)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .autocapitalization(.allCharacters)
                .onChange(of: inviteCode) { newValue in
                    if newValue.count == 6 {
                        validateInvite()
                    }
                }
            
            if isValidating {
                ProgressView("Validating...")
            }
            
            if let result = validationResult {
                if result.valid {
                    VStack {
                        Text("✅ Valid Invite")
                            .foregroundColor(.green)
                            .fontWeight(.bold)
                        
                        if let inviter = result.inviter {
                            Text("Invited by: \(inviter.displayName ?? "Unknown")")
                        }
                        
                        if let orgName = result.organizationName {
                            Text("Organization: \(orgName)")
                        }
                        
                        Button("Join App") {
                            redeemInvite()
                        }
                        .buttonStyle(.borderedProminent)
                    }
                } else {
                    Text("❌ \(result.message)")
                        .foregroundColor(.red)
                }
            }
        }
        .padding()
        .alert("Error", isPresented: $showError) {
            Button("OK") { }
        } message: {
            Text("Unable to validate invite code")
        }
    }
    
    private func validateInvite() {
        guard inviteCode.count == 6 else { return }
        
        isValidating = true
        
        Task {
            let result = await ContactsService.shared.invites.validateCode(code: inviteCode)
            
            DispatchQueue.main.async {
                self.isValidating = false
                self.validationResult = result
                if result == nil {
                    self.showError = true
                }
            }
        }
    }
    
    private func redeemInvite() {
        // Implement redemption logic with your user ID
        Task {
            let success = await ContactsService.shared.invites.redeemInvite(
                code: inviteCode,
                for: "current_user_id"
            )
            
            if success {
                // Navigate to main app
            } else {
                // Show error
            }
        }
    }
}

Invite Analytics Dashboard

Display invite performance and user relationships:

struct InviteAnalyticsView: View {
    @State private var invitedUsers: [Contact] = []
    @State private var invitedBy: Contact?
    @State private var remainingInvites: Int = 0
    @State private var isLoading = true
    
    var body: some View {
        NavigationView {
            List {
                Section("Your Invite Status") {
                    HStack {
                        Text("Remaining Invites")
                        Spacer()
                        Text("\(remainingInvites)")
                            .fontWeight(.bold)
                    }
                    
                    if let inviter = invitedBy {
                        HStack {
                            Text("Invited by")
                            Spacer()
                            Text(inviter.displayName ?? "Unknown")
                        }
                    }
                }
                
                if !invitedUsers.isEmpty {
                    Section("People You've Invited (\(invitedUsers.count))") {
                        ForEach(invitedUsers, id: \.contactId) { user in
                            HStack {
                                VStack(alignment: .leading) {
                                    Text(user.displayName ?? "Unknown")
                                        .fontWeight(.medium)
                                    if let email = user.cmContact?.emailAddresses.first?.value {
                                        Text(email)
                                            .font(.caption)
                                            .foregroundColor(.secondary)
                                    }
                                }
                                Spacer()
                                Image(systemName: "checkmark.circle.fill")
                                    .foregroundColor(.green)
                            }
                        }
                    }
                }
            }
            .navigationTitle("Invite Analytics")
            .refreshable {
                await loadAnalytics()
            }
        }
        .onAppear {
            Task {
                await loadAnalytics()
            }
        }
    }
    
    private func loadAnalytics() async {
        let cm = ContactsService.shared
        
        async let invited = cm.invites.invited()
        async let inviter = cm.invites.invitedBy()
        async let remaining = cm.invites.remaining()
        
        let (invitedList, inviterContact, remainingCount) = await (invited, inviter, remaining)
        
        DispatchQueue.main.async {
            self.invitedUsers = invitedList
            self.invitedBy = inviterContact
            self.remainingInvites = remainingCount
            self.isLoading = false
        }
    }
}

Best Practices

Invite Limit Management

  • Start Conservative: Begin with 3-5 invites per user to create scarcity
  • Reward Quality: Give additional invites to users whose invitees become active
  • Monitor Growth: Adjust limits based on server capacity and user acquisition goals

User Experience

  • Clear Value Proposition: Explain why users should invite friends
  • Social Proof: Show mutual connections and successful invitations
  • Immediate Connection: Help invited users connect with their inviter quickly

Growth Optimization

  • Track Conversion: Monitor invite-to-signup and invite-to-active-user rates
  • A/B Test Messages: Experiment with different invite email templates
  • Analyze Networks: Identify users who bring high-quality new members

Privacy Considerations

  • Minimal Exposure: Invite codes contain no personal information
  • User Control: Allow users to disable invite notifications
  • Data Retention: Implement policies for invite relationship data

Error Handling

Common error scenarios and how to handle them:

// Handle invite validation errors
let validation = await cm.invites.validateCode(code: userInput)

if let result = validation {
    if !result.valid {
        switch result.message {
        case "Invalid invite code format":
            // Show format help (6 characters, alphanumeric)
            break
        case "Invite code expired":
            // Suggest requesting new invite
            break
        case "Invite limit reached":
            // Explain invite limits
            break
        default:
            // Generic error message
            break
        }
    }
} else {
    // Network or service error
    // Show retry option
}

// Handle bulk email errors
let emailResult = await cm.invites.sendBulkInviteEmails(
    contacts: selectedContacts,
    emailBody: customMessage
)

if let result = emailResult {
    if !result.success {
        if result.message.contains("verified email sending domain") {
            // Guide user to domain verification
        } else if result.message.contains("Invalid request data") {
            // Check contact data quality
        }
    }
}

For more information about implementing invite features in your application, visit contactsmanager.io.