Github-Obsidian/02 - Tech Projects/Coding Projects/2026.03.Apple Reminders → N...

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