diff --git a/app/views/faq/index.md b/app/views/faq/index.md index 5364746428..d955519d60 100644 --- a/app/views/faq/index.md +++ b/app/views/faq/index.md @@ -1925,19 +1925,23 @@ In order to use calendar support, the **user** must be the email address of an a ### Do you support tasks and reminders (CalDAV VTODO) -Yes, as of \[INSERT DATE] we have added CalDAV VTODO support for tasks and reminders. This uses the same server as our calendar support: `caldav.forwardemail.net`. +Yes, as of October 14, 2025 we have added CalDAV VTODO support for tasks and reminders. This uses the same server as our calendar support: `caldav.forwardemail.net`. -Our CalDAV server supports both calendar events (VEVENT) and tasks (VTODO) components, with automatic separation into appropriate calendar collections: +Our CalDAV server supports both calendar events (VEVENT) and tasks (VTODO) components using **unified calendars**. This means each calendar can contain both events and tasks, providing maximum flexibility and compatibility across all CalDAV clients. -* **Calendar events** go into your main "Calendar" collection -* **Tasks/reminders** go into a separate "Reminders" collection +**How calendars and lists work:** + +* **Each calendar supports both events and tasks** - You can add events, tasks, or both to any calendar +* **Apple Reminders lists** - Each list you create in Apple Reminders becomes a separate calendar on the server +* **Multiple calendars** - You can create as many calendars as you need, each with its own name, color, and organization +* **Cross-client sync** - Tasks and events sync seamlessly between all compatible clients **Supported task clients:** * **macOS Reminders** - Full native support for task creation, editing, completion, and sync * **iOS Reminders** - Full native support across all iOS devices * **Tasks.org (Android)** - Popular open-source task manager with CalDAV sync -* **Thunderbird with Lightning** - Task support in desktop email client +* **Thunderbird** - Task and calendar support in desktop email client * **Any CalDAV-compatible task manager** - Standard VTODO component support **Task features supported:** @@ -1949,6 +1953,8 @@ Our CalDAV server supports both calendar events (VEVENT) and tasks (VTODO) compo * Recurring tasks * Task descriptions and notes * Multi-device synchronization +* Subtasks with RELATED-TO property +* Task reminders with VALARM The login credentials are the same as for calendar support: @@ -1957,7 +1963,12 @@ The login credentials are the same as for calendar support: | Username | `user@example.com` | Email address of an alias that exists for the domain at My Account Domains. | | Password | `************************` | Alias-specific generated password. | -**Important:** Tasks and calendar events are kept in separate collections to ensure proper client compatibility, especially with Apple devices that expect dedicated task calendars. +**Important notes:** + +* **Each Reminders list is a separate calendar** - When you create a new list in Apple Reminders, it creates a new calendar on the CalDAV server +* **Thunderbird users** - You'll need to manually subscribe to each calendar/list you want to sync, or use the calendar home URL: `https://caldav.forwardemail.net/dav/your-email@domain.com/` +* **Apple users** - Calendar discovery happens automatically, so all your calendars and lists will appear in Calendar.app and Reminders.app +* **Unified calendars** - All calendars support both events and tasks, giving you flexibility in how you organize your data ### Do you support contacts (CardDAV) diff --git a/caldav-server.js b/caldav-server.js index 3cf10a78d7..117780f4fc 100644 --- a/caldav-server.js +++ b/caldav-server.js @@ -1434,21 +1434,9 @@ class CalDAV extends API { // if (calendar) return calendar; // safeguard - // Determine supported components based on calendar name/type - // Default: support both events and tasks - let has_vevent = true; - let has_vtodo = true; - - // Task/reminder-only calendars (only if specifically named) - if ( - name === 'DEFAULT_TASK_CALENDAR_NAME' || - I18N_SET_REMINDERS.has(name) || - I18N_SET_TASKS.has(name) - ) { - has_vevent = false; // Tasks only - has_vtodo = true; - } - + // All calendars support both events and tasks (unified calendar approach) + // This ensures compatibility across all CalDAV clients (Apple, Thunderbird, etc.) + // and aligns with the default calendar creation behavior (see ensureDefaultCalendars) return Calendars.create({ // db virtual helper instance: this, @@ -1467,8 +1455,8 @@ class CalDAV extends API { url: config.urls.web, readonly: false, synctoken: `${config.urls.web}/ns/sync-token/1`, - has_vevent, - has_vtodo + has_vevent: true, // Support both events and tasks + has_vtodo: true }); } diff --git a/test/caldav/index.js b/test/caldav/index.js index 6e9efbd6e3..0c5491fa48 100644 --- a/test/caldav/index.js +++ b/test/caldav/index.js @@ -1405,7 +1405,50 @@ test('single ICS file with both VEVENT and VTODO components', async (t) => { // Use the unified calendar (first calendar) const unifiedCal = calendars[0]; - // Upload the file with both components + // First, explicitly verify the unified calendar supports both VEVENT and VTODO + // by successfully uploading a pure VEVENT file + const eventOnlyIcs = await fsp.readFile( + path.join(__dirname, 'data', '1.ics'), + 'utf8' + ); + const eventOnlyUrl = new URL('test-vevent-support.ics', unifiedCal.url).href; + const eventOnlyResponse = await createObject({ + url: eventOnlyUrl, + data: eventOnlyIcs, + headers: { + 'content-type': 'text/calendar; charset=utf-8', + ...t.context.authHeaders + } + }); + t.true( + eventOnlyResponse.ok, + 'Unified calendar should accept VEVENT (has_vevent=true)' + ); + + // Then verify it supports VTODO by uploading a pure VTODO file + const todoOnlyIcs = await fsp.readFile( + path.join(__dirname, 'data', 'vtodo-1.ics'), + 'utf8' + ); + const todoOnlyUrl = new URL('test-vtodo-support.ics', unifiedCal.url).href; + const todoOnlyResponse = await createObject({ + url: todoOnlyUrl, + data: todoOnlyIcs, + headers: { + 'content-type': 'text/calendar; charset=utf-8', + ...t.context.authHeaders + } + }); + t.true( + todoOnlyResponse.ok, + 'Unified calendar should accept VTODO (has_vtodo=true)' + ); + + // Clean up the test files + await deleteObject({ url: eventOnlyUrl, headers: t.context.authHeaders }); + await deleteObject({ url: todoOnlyUrl, headers: t.context.authHeaders }); + + // Now upload the file with both components const objectUrl = new URL('mixed-components.ics', unifiedCal.url).href; const response = await createObject({ url: objectUrl, @@ -1445,6 +1488,15 @@ test('single ICS file with both VEVENT and VTODO components', async (t) => { 'Should preserve VTODO summary' ); + // This test validates multiple aspects of mixed component support: + // 1. EXPLICITLY verifies has_vevent=true by uploading a pure VEVENT file (must succeed) + // 2. EXPLICITLY verifies has_vtodo=true by uploading a pure VTODO file (must succeed) + // 3. The unified calendar (created with has_vevent=true, has_vtodo=true in caldav-server.js:334-335) + // successfully accepts ICS files containing both VEVENT and VTODO components + // 4. Both components are preserved in the stored ICS data + // 5. Per calendar-events.js pre-validate hook (lines 160-167), when both components exist, + // the CalendarEvent.componentType field is set to 'VEVENT' for backward compatibility + // Clean up await deleteObject({ url: objectUrl, headers: t.context.authHeaders }); });