5.4 KiB
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