Files
metabuilder/services/email_service/docs/PHASE7_FOLDERS_API.md
2026-03-09 22:30:41 +00:00

17 KiB

Phase 7: Email Folders API Documentation

Overview

Phase 7 implements complete email folder/mailbox management endpoints with:

  • Folder CRUD operations
  • Folder hierarchy support (parent/child relationships)
  • Special folder types (Inbox, Sent, Drafts, Trash, Spam)
  • Message counts (unread and total) with sync tracking
  • Multi-tenant safety and row-level access control
  • Comprehensive error handling and validation

API Endpoints

1. List Folders with Message Counts

Endpoint: GET /api/accounts/:id/folders

Authentication: Required (X-Tenant-ID, X-User-ID)

Query Parameters:

  • tenant_id (string, required): Tenant ID for multi-tenant filtering
  • user_id (string, required): User ID for row-level access control
  • parent_id (string, optional): Filter by parent folder for hierarchy
  • include_counts (boolean, optional, default=true): Include message counts

Response (200 OK):

{
  "folders": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "tenantId": "550e8400-e29b-41d4-a716-446655440001",
      "userId": "550e8400-e29b-41d4-a716-446655440002",
      "accountId": "550e8400-e29b-41d4-a716-446655440003",
      "folderName": "INBOX",
      "displayName": "Inbox",
      "parentFolderId": null,
      "folderType": "inbox",
      "imapName": "INBOX",
      "isSystemFolder": true,
      "unreadCount": 42,
      "totalCount": 157,
      "isSelectable": true,
      "hasChildren": false,
      "isVisible": true,
      "lastSyncedAt": 1706033200000,
      "createdAt": 1706033200000,
      "updatedAt": 1706033200000,
      "children": []
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440004",
      "tenantId": "550e8400-e29b-41d4-a716-446655440001",
      "userId": "550e8400-e29b-41d4-a716-446655440002",
      "accountId": "550e8400-e29b-41d4-a716-446655440003",
      "folderName": "Projects",
      "displayName": "My Projects",
      "parentFolderId": null,
      "folderType": "custom",
      "imapName": "Projects",
      "isSystemFolder": false,
      "unreadCount": 5,
      "totalCount": 23,
      "isSelectable": true,
      "hasChildren": true,
      "isVisible": true,
      "lastSyncedAt": 1706033200000,
      "createdAt": 1706033200000,
      "updatedAt": 1706033200000,
      "children": []
    }
  ],
  "count": 2,
  "accountId": "550e8400-e29b-41d4-a716-446655440003"
}

Error Responses:

  • 400 Bad Request: Missing tenant_id or user_id
  • 404 Not Found: Account does not exist or user has no access
  • 500 Internal Server Error: Database or server error

2. Create Folder

Endpoint: POST /api/accounts/:id/folders

Authentication: Required (X-Tenant-ID, X-User-ID headers)

Request Body:

{
  "folderName": "Projects",
  "displayName": "My Projects",
  "parentFolderId": null,
  "folderType": "custom",
  "imapName": "Projects",
  "isSystemFolder": false
}

Field Descriptions:

  • folderName (string, required): Internal folder name (max 255 chars)
  • displayName (string, optional): User-friendly folder name (defaults to folderName)
  • parentFolderId (string, optional): Parent folder ID for nesting
  • folderType (string, optional, default='custom'): Type of folder
    • Valid values: inbox, sent, drafts, trash, spam, custom
  • imapName (string, optional): IMAP path (e.g., "Projects" or "Parent/Projects")
  • isSystemFolder (boolean, optional, default=false): System folders cannot be deleted

Response (201 Created):

{
  "id": "550e8400-e29b-41d4-a716-446655440004",
  "tenantId": "550e8400-e29b-41d4-a716-446655440001",
  "userId": "550e8400-e29b-41d4-a716-446655440002",
  "accountId": "550e8400-e29b-41d4-a716-446655440003",
  "folderName": "Projects",
  "displayName": "My Projects",
  "parentFolderId": null,
  "folderType": "custom",
  "imapName": "Projects",
  "isSystemFolder": false,
  "unreadCount": 0,
  "totalCount": 0,
  "isSelectable": true,
  "hasChildren": false,
  "isVisible": true,
  "lastSyncedAt": null,
  "createdAt": 1706033200000,
  "updatedAt": 1706033200000,
  "children": []
}

Error Responses:

  • 400 Bad Request: Invalid request body or validation errors
  • 401 Unauthorized: Missing X-Tenant-ID or X-User-ID header
  • 404 Not Found: Account does not exist
  • 409 Conflict: Folder with same name already exists
  • 500 Internal Server Error: Database or server error

Validation Rules:

  • folderName: Required, non-empty, max 255 characters
  • folderType: Must be one of inbox, sent, drafts, trash, spam, custom
  • Duplicate folder names within account are rejected

3. Get Folder Details

Endpoint: GET /api/accounts/:id/folders/:folderId

Authentication: Required (tenant_id and user_id query params)

Query Parameters:

  • tenant_id (string, required): Tenant ID
  • user_id (string, required): User ID
  • include_hierarchy (boolean, optional, default=false): Include parent path and children

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440004",
  "tenantId": "550e8400-e29b-41d4-a716-446655440001",
  "userId": "550e8400-e29b-41d4-a716-446655440002",
  "accountId": "550e8400-e29b-41d4-a716-446655440003",
  "folderName": "Projects",
  "displayName": "My Projects",
  "parentFolderId": null,
  "folderType": "custom",
  "imapName": "Projects",
  "isSystemFolder": false,
  "unreadCount": 5,
  "totalCount": 23,
  "isSelectable": true,
  "hasChildren": true,
  "isVisible": true,
  "lastSyncedAt": 1706033200000,
  "createdAt": 1706033200000,
  "updatedAt": 1706033200000,
  "children": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440005",
      "folderName": "Q1",
      "displayName": "Q1 2024",
      "parentFolderId": "550e8400-e29b-41d4-a716-446655440004"
    }
  ]
}

Error Responses:

  • 400 Bad Request: Missing tenant_id or user_id
  • 404 Not Found: Folder does not exist or user has no access
  • 500 Internal Server Error: Database or server error

4. Update Folder

Endpoint: PUT /api/accounts/:id/folders/:folderId

Authentication: Required (X-Tenant-ID, X-User-ID headers)

Request Body (all fields optional):

{
  "displayName": "Important Projects",
  "unreadCount": 10,
  "totalCount": 50,
  "isVisible": true,
  "syncStateUidvalidity": "123456789",
  "syncStateUidnext": 1000
}

Field Descriptions:

  • displayName (string, optional): New display name (cannot be changed for system folders)
  • unreadCount (integer, optional): Number of unread messages
  • totalCount (integer, optional): Total number of messages
  • isVisible (boolean, optional): Visibility flag
  • syncStateUidvalidity (string, optional): IMAP UIDVALIDITY state
  • syncStateUidnext (integer, optional): IMAP UIDNEXT state

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440004",
  "displayName": "Important Projects",
  "unreadCount": 10,
  "totalCount": 50,
  ...
}

Error Responses:

  • 400 Bad Request: Invalid request body or validation errors
  • 401 Unauthorized: Missing X-Tenant-ID or X-User-ID header
  • 403 Forbidden: Attempted to rename system folder
  • 404 Not Found: Folder does not exist
  • 500 Internal Server Error: Database or server error

Validation Rules:

  • displayName: Max 255 characters
  • unreadCount, totalCount: Must be non-negative integers
  • System folders (isSystemFolder: true) cannot have displayName changed
  • Counts cannot be negative

5. Delete Folder

Endpoint: DELETE /api/accounts/:id/folders/:folderId

Authentication: Required (tenant_id and user_id query params)

Query Parameters:

  • tenant_id (string, required): Tenant ID
  • user_id (string, required): User ID
  • hard_delete (boolean, optional, default=false): Permanently delete instead of soft delete

Response (200 OK):

{
  "message": "Folder deleted successfully",
  "id": "550e8400-e29b-41d4-a716-446655440004",
  "hardDeleted": false
}

Error Responses:

  • 400 Bad Request: Missing tenant_id or user_id
  • 403 Forbidden: Attempted to delete system folder
  • 404 Not Found: Folder does not exist
  • 500 Internal Server Error: Database or server error

Deletion Behavior:

  • Soft Delete (default): Sets isVisible: false, folder can be recovered
  • Hard Delete: Permanently removes folder from database, non-recoverable
  • System folders (Inbox, Sent, Drafts, Trash, Spam) cannot be deleted

6. List Folder Messages

Endpoint: GET /api/accounts/:id/folders/:folderId/messages

Authentication: Required (tenant_id and user_id query params)

Query Parameters:

  • tenant_id (string, required): Tenant ID
  • user_id (string, required): User ID
  • limit (integer, optional, default=50): Page size (1-500)
  • offset (integer, optional, default=0): Number of messages to skip
  • sort_by (string, optional, default=date): Sort field (date, from, subject)
  • sort_order (string, optional, default=desc): Sort direction (asc, desc)
  • filter_unread (boolean, optional): Show unread messages only
  • search_query (string, optional): Search in subject and from

Response (200 OK):

{
  "messages": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440006",
      "folderId": "550e8400-e29b-41d4-a716-446655440004",
      "from": "alice@example.com",
      "to": "bob@example.com",
      "subject": "Project Update",
      "body": "Here is the latest update...",
      "receivedAt": 1706033200000,
      "isUnread": false,
      "hasAttachments": true
    }
  ],
  "count": 1,
  "total": 23,
  "limit": 50,
  "offset": 0,
  "folderId": "550e8400-e29b-41d4-a716-446655440004",
  "accountId": "550e8400-e29b-41d4-a716-446655440003",
  "note": "Message listing requires EmailMessage model (Phase 8)"
}

Error Responses:

  • 400 Bad Request: Invalid pagination parameters
  • 401 Unauthorized: Missing tenant_id or user_id
  • 404 Not Found: Folder does not exist
  • 500 Internal Server Error: Database or server error

Pagination Rules:

  • limit: Must be between 1 and 500
  • offset: Must be non-negative
  • Default page size: 50 messages

Note: Message listing is implemented as a placeholder. Full message retrieval requires Phase 8 EmailMessage model implementation.


7. Get Folder Hierarchy

Endpoint: GET /api/accounts/:id/folders/:folderId/hierarchy

Authentication: Required (tenant_id and user_id query params)

Query Parameters:

  • tenant_id (string, required): Tenant ID
  • user_id (string, required): User ID

Response (200 OK):

{
  "folder": {
    "id": "550e8400-e29b-41d4-a716-446655440004",
    "folderName": "Q1",
    "displayName": "Q1 2024",
    "parentFolderId": "550e8400-e29b-41d4-a716-446655440007",
    ...
  },
  "parentPath": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "displayName": "Projects"
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440007",
      "displayName": "2024"
    }
  ],
  "children": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440008",
      "folderName": "Issues",
      "displayName": "Q1 Issues",
      "parentFolderId": "550e8400-e29b-41d4-a716-446655440004"
    }
  ]
}

Error Responses:

  • 400 Bad Request: Missing tenant_id or user_id
  • 404 Not Found: Folder does not exist
  • 500 Internal Server Error: Database or server error

Folder Types

Special Folders

These folders are system-managed and have special behavior:

Type Purpose Can Delete Can Rename
inbox Incoming mail ✗ No ✗ No
sent Sent messages ✗ No ✗ No
drafts Draft messages ✗ No ✗ No
trash Deleted messages ✗ No ✗ No
spam Spam messages ✗ No ✗ No
custom User-created folder ✓ Yes ✓ Yes

Authentication & Multi-Tenancy

Header Authentication (POST, PUT)

X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440001
X-User-ID: 550e8400-e29b-41d4-a716-446655440002

Query Parameter Authentication (GET, DELETE)

GET /api/accounts/123/folders?tenant_id=xyz&user_id=abc

Multi-Tenant Safety

  • All queries filter by tenant_id and user_id
  • Row-level access control prevents cross-tenant data access
  • Missing credentials return 401 Unauthorized
  • Access to non-owned resources returns 404 Not Found

Error Handling

Standard Error Response Format

{
  "error": "Error Category",
  "message": "Detailed error message"
}

HTTP Status Codes

Code Meaning
200 OK - Request succeeded
201 Created - Folder successfully created
400 Bad Request - Invalid input or validation error
401 Unauthorized - Missing or invalid credentials
403 Forbidden - Operation not allowed on this resource
404 Not Found - Resource does not exist
409 Conflict - Duplicate or conflicting resource
500 Internal Server Error - Server error

Data Models

EmailFolder

interface EmailFolder {
  id: string;                          // UUID
  tenantId: string;                    // UUID - multi-tenant identifier
  userId: string;                      // UUID - user who owns folder
  accountId: string;                   // UUID - email account
  folderName: string;                  // e.g., "INBOX", "Projects"
  displayName: string;                 // User-visible name
  parentFolderId: string | null;       // For hierarchy
  folderType: 'inbox' | 'sent' | 'drafts' | 'trash' | 'spam' | 'custom';
  imapName: string;                    // IMAP path, e.g., "INBOX" or "Projects/Q1"
  isSystemFolder: boolean;             // Cannot delete/rename if true
  unreadCount: number;                 // Unread message count
  totalCount: number;                  // Total message count
  isSelectable: boolean;               // Can contain messages (IMAP)
  hasChildren: boolean;                // Has subfolders
  isVisible: boolean;                  // Visible in UI (soft-delete flag)
  lastSyncedAt: number | null;         // Milliseconds since epoch
  syncStateUidvalidity: string | null; // IMAP UIDVALIDITY for sync tracking
  syncStateUidnext: number | null;     // IMAP UIDNEXT for sync tracking
  createdAt: number;                   // Milliseconds since epoch
  updatedAt: number;                   // Milliseconds since epoch
}

Examples

Create a Custom Folder with Hierarchy

curl -X POST http://localhost:5000/api/accounts/acc-123/folders \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: tenant-456" \
  -H "X-User-ID: user-789" \
  -d '{
    "folderName": "Q1",
    "displayName": "Q1 2024",
    "parentFolderId": "projects-folder-id",
    "folderType": "custom",
    "imapName": "Projects/Q1"
  }'

Update Folder Message Counts

curl -X PUT http://localhost:5000/api/accounts/acc-123/folders/folder-id \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: tenant-456" \
  -H "X-User-ID: user-789" \
  -d '{
    "unreadCount": 15,
    "totalCount": 42,
    "syncStateUidvalidity": "123456789",
    "syncStateUidnext": 1000
  }'

List Folders with Children

curl -X GET "http://localhost:5000/api/accounts/acc-123/folders?tenant_id=tenant-456&user_id=user-789"

Get Folder Hierarchy

curl -X GET "http://localhost:5000/api/accounts/acc-123/folders/folder-id/hierarchy?tenant_id=tenant-456&user_id=user-789"

Delete Folder (Soft Delete)

curl -X DELETE "http://localhost:5000/api/accounts/acc-123/folders/folder-id?tenant_id=tenant-456&user_id=user-789"

Delete Folder (Hard Delete)

curl -X DELETE "http://localhost:5000/api/accounts/acc-123/folders/folder-id?tenant_id=tenant-456&user_id=user-789&hard_delete=true"

Implementation Details

Database Schema

The EmailFolder model uses SQLAlchemy with PostgreSQL:

  • Composite index on (user_id, tenant_id) for multi-tenant queries
  • Index on (account_id, tenant_id) for account filtering
  • Index on (folder_type, tenant_id) for special folder lookups
  • Index on (parent_folder_id, account_id) for hierarchy
  • Soft delete via isVisible column

Folder Hierarchy

  • Folders can be nested using parentFolderId
  • hasChildren flag indicates if folder has subfolders
  • get_hierarchy_path() returns full ancestor chain
  • get_child_folders() returns direct children only

Message Counting

  • unreadCount: Number of unread messages in folder
  • totalCount: Total number of messages in folder
  • Counts updated by increment/decrement methods
  • Sync timestamp auto-updated when counts change

Soft Delete vs Hard Delete

  • Soft Delete: Sets isVisible: false, folder preserved in database
  • Hard Delete: Permanently removes folder (non-recoverable)
  • By default, only soft deletes (lists exclude invisible folders)

Testing

Run the comprehensive test suite:

pytest tests/test_folders.py -v --cov=src/routes/folders

Test coverage includes:

  • All CRUD operations
  • Multi-tenant safety
  • Error handling and validation
  • Folder hierarchy operations
  • Message counting and sync state
  • Special folder constraints

Total: 30+ test cases covering all endpoints and edge cases.