Project Summary & Learnings  |  March 2026 # What We Built A one-way automation that syncs incomplete tasks from Apple Reminders ("To Do" list) to a Notion database, with deduplication to prevent duplicate entries. ## Final Architecture Reminders app closed   → Shortcuts automation triggers      → Scriptable script runs (EventKit)         → Fetches all incomplete tasks from "To Do" list            → POST to n8n webhook               → Code node parses + structures tasks                  → For each task:                     → Query Notion by iCloud UID                     → If not found → Create Notion page                     → If found → Skip (no duplicate) ## Data Flow Per Task - UID (unique identifier from EventKit) - Title - Due date (ISO 8601 format) - Notes - Status → hardcoded as "To Do" on creation ## Completion Sync (Reminders → Notion) A second Scriptable script checks for tasks completed in the last 24 hours and updates their Notion status to "Done" via a separate n8n code node routing on action: "complete". # Approaches Tried ## Option A — iCloud CalDAV Polling (abandoned) The original plan was to poll iCloud's CalDAV server from n8n every 5 minutes. This is reliable and device-independent but hit a hard wall: - Apple migrated modern Reminders lists to CloudKit (iOS 13+) - CalDAV only exposes legacy lists — flagged as "Reminders ⚠️" in the calendar listing - Custom lists like "To Do" created in the modern format are completely invisible to CalDAV - No workaround exists — this is an Apple architectural decision ## Option B — Apple Shortcuts + n8n Webhook (partial) Shortcuts has native access to modern Reminders but hit several limitations: - No "when reminder is added" automation trigger exists in iOS - Shortcuts bundles list variables into one concatenated string instead of firing per item - Workarounds (Text actions, Set Variable, loop structures) all produced the same bundling behaviour - Date + title pairing by index breaks when tasks have no due date — lists become misaligned ## Option C — Scriptable + EventKit (final solution) Scriptable is a free iOS app that exposes native iPhone APIs including EventKit. Writing ~20 lines of JavaScript gave full programmatic access to Reminders with: - Correct title per task - ISO 8601 due date per task (null if not set) - Unique identifier per task (essential for deduplication) - Notes per task Triggered via Shortcuts automation on Reminders app close. # Technical Learnings ## Apple / iOS - Modern Reminders (iOS 13+) use CloudKit — NOT CalDAV. CalDAV only works for legacy unconverted lists. - Shortcuts cannot trigger on "reminder added" — only on app open/close or time-based. - Shortcuts visual variable system is unreliable for extracting typed properties from list objects — it bundles values instead of iterating correctly. - Scriptable + EventKit is the correct tool for programmatic Reminders access on iOS. - EventKit's Reminder.allIncomplete([list]) returns full objects with all properties including identifier, dueDate, notes, completionDate. ## n8n - n8n Cloud 2.9.4 Code nodes do not support $http, fetch, or this.getCredentials() — use helpers.httpRequest() instead. - helpers.httpRequest() auto-parses JSON responses — do not wrap in JSON.parse(). - Test webhook URL (webhook-test/) only stays open for one call after clicking Execute Workflow — use production URL + activate toggle for persistent workflows. - Notion node with 0 results outputs nothing downstream — IF nodes cannot branch on empty. Use a Code node with direct API calls to handle both search and create in one step. - Custom HTTP methods like REPORT and PROPFIND are not available in the HTTP Request node dropdown — use a Code node with helpers.httpRequest() instead. ## Notion API - Deduplication requires storing the source system's UID (iCloud UID) as a rich_text property in Notion. - Status fields use { status: { name: "To Do" } } format — not select or text. - Date fields require ISO 8601 format: { date: { start: "2026-03-10T00:00:00.000Z" } }. - Integration must be explicitly connected to each database via Connections in Notion UI — otherwise API returns 401. # Final Stack - Reminders access: Scriptable (iOS) - Automation trigger: Apple Shortcuts - Workflow engine: n8n Cloud 2.9.4 - Task database: Notion API (2022-06-28) # Future Extensions - Notion → Reminders bidirectional sync (requires n8n polling + push notification trigger e.g. ntfy.sh or Pushover) - Optimise to only sync tasks created/modified since last run (store last sync timestamp) - Migrate to self-hosted n8n on Hetzner VPS (~€5/month) — migration is a half-day job: export JSON, re-authenticate credentials, update webhook URLs - Add error handling and Slack/email alerts for failed syncs Key credentials & config n8n webhook: https://weisstrevor.app.n8n.cloud/webhook/c68fdc4a-0e56-4ae2-8c5f-14e339846cbe Notion DB: 2872f572ad7d8089beb4f24380180a12 iCloud CalDAV: p25-caldav.icloud.com / Apple ID: 2004413068