Skip to content

Invitation System ​

Overview ​

The invitation system provides a flexible authentication mechanism that allows communities to control user registration. It supports two modes:

  1. Open Registration Mode: Anyone can register without restrictions
  2. Invite-Only Mode: New users require an invitation code to register

The system includes email notifications and management tools. There is no waitlist: in invite-only mode, a visitor without a valid invitation code cannot register.

Key Features ​

For Administrators ​

  • Toggle between open and invite-only registration
  • View system-wide invitation statistics

For Users ​

  • Generate invitation codes
  • Share invitations via QR code, email, SMS, or direct links
  • Manage sent invitations (view status, revoke pending invitations)
  • Track invitation acceptance

For New Users ​

  • Register with valid invitation codes
  • Receive clear messaging about registration requirements

Architecture ​

Backend Components ​

Models ​

  • Invitation (backend/app/Models/Invitation.php): Core invitation model with relationships to inviter and recipient users
  • Settings (backend/app/Models/Settings.php): Key-value store for system configuration

Services ​

  • InvitationService (backend/app/Services/InvitationService.php): Business logic for invitation generation, validation, and acceptance
  • SettingsService (backend/app/Services/SettingsService.php): Handles system settings access and caching

Controllers ​

  • InvitationController (backend/app/Http/Controllers/InvitationController.php): API endpoints for invitation management
  • SettingsController (backend/app/Http/Controllers/SettingsController.php): Public settings endpoint

Notifications ​

  • InvitationToEmail (backend/app/Notifications/InvitationToEmail.php): Email notifications for direct invitations

Database ​

  • Migrations:
    • create_invitations_table.php - Stores invitation codes and status
    • create_settings_table.php - System configuration

Frontend Components ​

API Layer ​

  • invite-system.ts (frontend/src/api/invite-system.ts): TypeScript API client for all invitation endpoints

Components ​

  • InvitationShare (frontend/src/components/InvitationShare.tsx): Share invitations via multiple channels
  • InvitationQRCode (frontend/src/components/InvitationQRCode.tsx): Generate and download QR codes

Pages ​

  • InvitationsPage (frontend/src/pages/invitations/InvitationsPage.tsx): Dashboard for managing invitations

Hooks ​

  • useInviteSystem (frontend/src/hooks/use-invite-system.ts): React hook for registration mode detection

API Endpoints ​

Public Endpoints ​

Get Public Settings ​

http
GET /api/settings/public

Response:

json
{
  "data": {
    "invite_only_enabled": false
  }
}

Validate Invitation Code ​

http
POST /api/invitations/validate
Content-Type: application/json

{
  "code": "abc123xyz"
}

Response:

json
{
  "data": {
    "valid": true,
    "inviter": {
      "name": "John Doe"
    },
    "expires_at": null
  }
}

Authenticated Endpoints ​

Generate Invitation ​

http
POST /api/invitations
Authorization: Bearer {token}
Content-Type: application/json

{
  "email": "recipient@example.com",  // optional
  "expires_at": "2024-12-31T23:59:59Z"  // optional
}

Response (201 Created):

json
{
  "data": {
    "id": 1,
    "code": "abc123xyz",
    "status": "pending",
    "expires_at": null,
    "invitation_url": "https://example.com/register?invitation_code=abc123xyz",
    "created_at": "2024-01-01T00:00:00Z",
    "recipient": null
  }
}

Rate Limiting: 10/hour endpoint throttle plus 10/day business limit per user (revoked invitations excluded from daily count)

List User Invitations ​

http
GET /api/invitations
Authorization: Bearer {token}

Response:

json
{
  "data": [
    {
      "id": 1,
      "code": "abc123xyz",
      "status": "pending",
      "expires_at": null,
      "invitation_url": "https://example.com/register?invitation_code=abc123xyz",
      "created_at": "2024-01-01T00:00:00Z",
      "recipient": null
    },
    {
      "id": 2,
      "code": "def456abc",
      "status": "accepted",
      "expires_at": null,
      "invitation_url": "https://example.com/register?invitation_code=def456abc",
      "created_at": "2024-01-02T00:00:00Z",
      "recipient": {
        "id": 2,
        "name": "Jane Doe",
        "email": "jane@example.com"
      }
    }
  ]
}

Revoke Invitation ​

http
DELETE /api/invitations/{id}
Authorization: Bearer {token}

Response (200 OK):

json
{
  "data": []
}

Error Responses:

  • 404 Not Found - Invitation not found or cannot be revoked

Get Invitation Statistics ​

http
GET /api/invitations/stats
Authorization: Bearer {token}

Response:

json
{
  "data": {
    "total": 10,
    "pending": 3,
    "accepted": 5,
    "expired": 1,
    "revoked": 1
  }
}

Registration Integration ​

Authentication Flow ​

The invitation system integrates with the registration process through the RegisterRequest validation:

php
// When invite-only mode is enabled
if (Settings::isInviteOnlyEnabled()) {
    $rules['invitation_code'] = ['required', new ValidInvitationCode()];
}

Frontend Registration Detection ​

The useInviteSystem hook automatically detects the registration mode:

typescript
const { mode, invitationCode, invitationValidation, error } = useInviteSystem();

// mode can be:
// - 'open-registration': Anyone can register
// - 'invite-only-no-code': Restricted, show the invite-only notice
// - 'invite-only-with-code': Restricted with valid code

Registration Modes ​

Open Registration Mode ​

  • Standard registration form
  • Optional invitation code can still be provided
  • No restrictions on new users

Invite-Only Mode (No Code) ​

  • Registration form and Google/Telegram sign-up buttons are hidden
  • An invite-only notice asks the visitor to get an invitation link from a current member
  • Google sign-in without a valid code redirects to /login?error=invite_only; Telegram returns the existing invite-only block

Invite-Only Mode (With Valid Code) ​

  • Registration form is displayed
  • Invitation code is pre-filled from URL parameter
  • Shows inviter information
  • Validates code before allowing registration

Email Notifications ​

Invitation Email ​

Sent when a user generates an invitation to a specific email address:

Subject: You're invited to join Meo Mai Moi!

Content:

  • Personalized greeting with inviter name
  • Call-to-action button with invitation link
  • Invitation code for manual entry
  • Expiration information (if applicable)

Email Templates ​

Custom email templates are located in:

  • backend/resources/views/vendor/mail/html/ - HTML templates
  • backend/resources/views/vendor/mail/text/ - Plain text templates

Configuration ​

Environment Variables ​

bash
# Enable invite-only mode (default: false)
INVITE_ONLY_ENABLED=false

# Email configuration for notifications
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME="${APP_NAME}"

Settings Management ​

Settings are stored in the settings table and cached for performance:

php
// Get setting
$inviteOnly = Settings::get('invite_only_enabled', 'false');

// Set setting
Settings::set('invite_only_enabled', 'true');

// Toggle setting
$newValue = Settings::toggle('invite_only_enabled');

// Helper method
$isEnabled = Settings::isInviteOnlyEnabled();

Rate Limiting ​

Invitation generation is rate-limited to prevent abuse:

  • User limit: 10 invitations per day (revoked invitations are excluded from this count)
  • Endpoint throttle: 10 requests/hour on creation, 20 requests/hour on revocation, plus the group-level authenticated limiter

See Rate Limiting for the full API rate limiting reference.

Testing ​

Backend Tests ​

Feature Tests ​

  • InvitationControllerTest: API endpoint testing
  • InviteSystemAuthTest: Registration flow with invitations
  • InviteSystemIntegrationTest: End-to-end workflows
  • SettingsControllerTest: Public settings endpoint

Unit Tests ​

  • InvitationServiceTest: Service layer logic
  • InvitationTest: Model behavior and scopes
  • SettingsTest: Settings model methods
  • SettingsServiceTest: Service layer caching

Running Tests ​

bash
cd backend
./vendor/bin/phpunit --testsuite=Feature --filter=Invitation
./vendor/bin/phpunit --testsuite=Unit --filter=Invitation

Frontend Tests ​

Component Tests ​

  • InvitationQRCode.test.tsx: QR code generation
  • InvitationShare.test.tsx: Share functionality

Page Tests ​

  • InvitationsPage.test.tsx: Invitation management UI

Hook Tests ​

  • use-invite-system.test.ts: Registration mode detection

Running Tests ​

bash
cd frontend
bun test

User Workflows ​

Workflow 1: Direct Invitation ​

  1. User generates invitation

    • Navigates to invitations page
    • Clicks "Generate Invitation"
    • Receives invitation link
  2. User shares invitation

    • Copies link directly
    • Or shares via QR code
    • Or sends via email/SMS through share dialog
  3. Recipient registers

    • Opens invitation link
    • Completes registration
    • Invitation is marked as accepted

Workflow 2: Open Registration with Optional Invitation ​

  1. User visits registration page

    • Normal registration form is displayed
    • No invitation code required
  2. User optionally uses invitation code

    • Enters invitation code (optional)
    • Registration proceeds normally
    • If code is provided, it's tracked

Security Considerations ​

Invitation Code Generation ​

  • Codes are 32-character random strings
  • Uniqueness is enforced at database level
  • Codes are non-sequential and unpredictable

Validation Rules ​

  • Invitation codes must be pending status
  • Codes must not be expired
  • Each code can only be used once
  • Users can only revoke their own invitations

Rate Limiting ​

  • Invitation generation limited per user
  • API endpoints have standard throttling

Authorization ​

  • Only authenticated users can generate invitations
  • Users can only view and manage their own invitations
  • Public endpoints only expose necessary information

Data Protection ​

  • Sensitive data is not exposed in public endpoints
  • Settings cache is cleared on updates

Troubleshooting ​

Common Issues ​

Invitation Codes Not Working ​

Symptoms: Invalid code error during registration

Solutions:

  1. Check invitation status (must be "pending")
  2. Verify expiration date hasn't passed
  3. Ensure invite-only mode is actually enabled
  4. Clear settings cache: php artisan cache:clear

Emails Not Sending ​

Symptoms: No confirmation emails received

Solutions:

  1. Verify mail configuration in .env
  2. Check mail logs: storage/logs/laravel.log
  3. Test mail connection: php artisan tinker
    php
    Mail::raw('Test', function($msg) {
        $msg->to('test@example.com')->subject('Test');
    });

Frontend Not Detecting Invite Mode ​

Symptoms: Wrong form displayed

Solutions:

  1. Check /api/settings/public endpoint response
  2. Verify settings in database
  3. Clear browser cache and cookies
  4. Check console for JavaScript errors

Database Issues ​

Symptoms: Migration errors

Solutions:

  1. Run migrations: php artisan migrate
  2. Check database connection
  3. Verify permissions on settings table

Future Enhancements ​

Potential improvements for the invitation system:

  1. Bulk Invitations: Generate multiple invitations at once
  2. Invitation Templates: Custom messages for invitations
  3. Invitation Groups: Tag invitations for organizational purposes
  4. Analytics Dashboard: Track invitation conversion rates
  5. Automatic Expiration: Background job to expire old invitations
  6. Invitation Limits: Per-user invitation quotas
  7. Social Sharing: Direct integration with social media platforms
  8. Referral Tracking: Track which users bring in the most new members
  9. Invitation History: Audit log for invitation activities

Support ​

For issues or questions about the invitation system:

  1. Check this documentation first
  2. Review the test files for usage examples
  3. Check the GitHub issues
  4. Create a new issue with detailed information