Spaces:
Sleeping
Bulk User Invitations via CSV
Overview
The bulk invitation feature allows admins to invite multiple users at once by uploading a CSV file. The system intelligently handles:
- Existing users in project → Skip (already members)
- Existing users not in project → Add to project team
- New users → Send invitations
CSV Format
Organization-Level Invitations
For inviting users to a client or contractor organization:
email,first_name,last_name,phone,role,invitation_method
john@example.com,John,Doe,+254712345678,field_agent,whatsapp
jane@example.com,Jane,Smith,+254798765432,dispatcher,email
Required columns:
email- User email addressrole- System role (field_agent, dispatcher, project_manager, etc.)
Optional columns:
first_name- First name (for personalization)last_name- Last namephone- Phone with country code (+254...)invitation_method- whatsapp, email, or both (default: whatsapp)
Project-Level Invitations
For inviting users directly to a project with role assignments:
email,first_name,last_name,phone,role,invitation_method,project_role,region,subcontractor
john@example.com,John,Doe,+254712345678,field_agent,whatsapp,Installer,Nairobi West,
jane@example.com,Jane,Smith,+254798765432,dispatcher,email,Site Supervisor,,
Additional columns for projects:
project_role- Role name or ID on the project (e.g., "Installer", "Technician")region- Region name or ID (optional, NULL = project-wide)subcontractor- Subcontractor name or ID (optional, NULL = main contractor)
API Workflow
Step 1: Analyze CSV
Upload CSV and get analysis of users:
POST /api/v1/invitations/bulk/analyze
Content-Type: multipart/form-data
csv_file: <file>
project_id: <uuid> (OR client_id OR contractor_id)
Response:
{
"total_rows": 50,
"valid_rows": 48,
"invalid_rows": 2,
"analysis": {
"existing_in_project": [
{
"email": "john@example.com",
"user_id": "uuid",
"name": "John Doe",
"current_role": "field_agent",
"status": "already_member"
}
],
"existing_not_in_project": [
{
"email": "jane@example.com",
"user_id": "uuid",
"name": "Jane Smith",
"current_role": "dispatcher",
"action": "add_to_project",
"csv_data": {...}
}
],
"new_users_to_invite": [
{
"email": "bob@example.com",
"action": "send_invitation",
"csv_data": {...}
}
],
"errors": [
{
"row": 5,
"email": "invalid-email",
"errors": ["Invalid email format"]
}
]
}
}
Step 2: Review & Execute
Frontend displays analysis to user for review. User confirms, then:
POST /api/v1/invitations/bulk/execute
Content-Type: application/json
{
"project_id": "uuid",
"users_to_add": [
{
"email": "jane@example.com",
"user_id": "uuid",
"csv_data": {...}
}
],
"users_to_invite": [
{
"email": "bob@example.com",
"csv_data": {...}
}
],
"bulk_operation_id": "BULK_20251211_123456"
}
Response:
{
"results": {
"users_added": {
"success": 10,
"failed": 0,
"details": [...]
},
"invitations_sent": {
"success": 35,
"failed": 2,
"details": [...]
}
},
"summary": {
"total_processed": 47,
"successful": 45,
"failed": 2
}
}
Download CSV Template
Get a pre-formatted CSV template:
GET /api/v1/invitations/bulk/template/download?template_type=project
Template types:
organization- For org-level invites (simpler)project- For project-level invites (includes project fields)
Authorization
All roles with invite_users permission can use bulk invitations:
Platform Admin:
- Can bulk invite to any context (organization or project)
Client Admin:
- Can bulk invite to their client organization only
Contractor Admin:
- Can bulk invite to their contractor organization only
Project Manager:
- Can bulk invite to their own projects
- Must invite to their contractor's organization
Dispatcher:
- Can bulk invite to projects in their contractor
- Must invite to their contractor's organization
Sales Manager:
- Can bulk invite to projects in their contractor
- Must invite to their contractor's organization
Note: Service-level authorization validates that users can only invite within their scope (own organization/projects)
Tracking
Bulk invitations are tracked in the invitation_metadata JSONB field:
{
"bulk_import": true,
"bulk_operation_id": "BULK_20251211_123456"
}
This allows filtering/reporting on bulk vs individual invitations without needing a separate table.
Validation Rules
- Email format validation
- Phone must start with + and country code
- Role must be valid enum value
- Project role/region/subcontractor must exist if specified
- Max 1000 rows per CSV (configurable via
MAX_BULK_INVITATION_ROWSenv var) - Duplicate detection: won't create duplicate pending invitations
Error Handling
- Invalid CSV rows are reported but don't block valid rows
- Each user operation (add/invite) is independent
- Partial success is supported (some succeed, some fail)
- Detailed error messages for each failure
Frontend Flow
- User uploads CSV
- Call
/analyzeendpoint - Display categorized results:
- "10 users will be added to project"
- "35 invitations will be sent"
- "5 users already in project (skipped)"
- "2 errors found"
- User reviews and optionally deselects users
- User confirms
- Call
/executeendpoint with selected users - Display results with success/failure breakdown
Implementation Notes
No Database Table Needed
- Analysis results are returned in-memory (not persisted)
- Frontend manages the review/confirmation flow
- Bulk operations tracked via
invitation_metadata.bulk_operation_idin existinguser_invitationstable - Simpler architecture, no session cleanup needed
Smart User Resolution
- Project roles, regions, and subcontractors can be specified by name or UUID
- Case-insensitive matching for names
- Validates that referenced entities exist and belong to the project
Files Created
src/app/schemas/bulk_invitation.py- Pydantic schemassrc/app/services/bulk_invitation_service.py- Business logicsrc/app/api/v1/bulk_invitations.py- API endpoints- Registered in
src/app/api/v1/router.py
Reuses Existing Infrastructure
- Uses existing
InvitationServicefor sending invitations - Uses existing
UserInvitationmodel with metadata tracking - Uses existing
ProjectTeammodel for adding users to projects - No new database migrations required