🚀 Executive Summary

TL;DR: Manually transcribing tasks between Apple Reminders and Todoist is inefficient. This guide provides a Python and AppleScript solution for a robust, two-way synchronization, leveraging unique sync IDs to automate task management and save valuable time.

🎯 Key Takeaways

  • The solution relies on Python 3.8+ with `todoist-api-python` for Todoist API interaction and `py-applescript` for programmatic control over Apple Reminders on macOS.
  • A `SYNC_ID_TAG` (e.g., “sync_id:”) embedded in task descriptions or notes is critical for establishing unique identifiers, preventing duplicate task creation, and enabling reliable two-way synchronization.
  • Dedicated projects/lists (e.g., “Siri Sync”) in both Todoist and Apple Reminders are recommended to contain the sync process, and `cron` on macOS is used to schedule the Python script for automated execution at regular intervals.

Syncing Apple Reminders to Todoist (Two-way)

Syncing Apple Reminders to Todoist (Two-way)

Alright, let’s talk about a pain point I lived with for far too long. I love using Siri on my Apple Watch to capture quick thoughts on the go— “Hey Siri, remind me to check the staging server deployment logs.” The problem? All my real, structured project work lives in Todoist. For months, my workflow involved manually transcribing these reminders from Apple’s ecosystem into my Todoist projects. It was a tedious, error-prone process that felt like a waste of valuable time. I finally decided to solve it, and now I’m sharing the setup.

This guide will walk you through building a robust, two-way sync script between Apple Reminders and Todoist. We’re not just dumping tasks from one place to another; we’re creating a system that keeps them in harmony. It’s a “set it and forget it” solution that has genuinely saved me a few hours each month and, more importantly, a ton of mental overhead.

Prerequisites

Before we dive in, make sure you have the following ready. This is a developer-focused solution, so I’ll assume you’re comfortable with the basics.

  • A Mac running macOS (this solution relies on AppleScript to interact with Reminders).
  • Python 3.8 or newer installed.
  • A Todoist account with API access (available on all plans, including free).
  • Familiarity with running Python scripts and scheduling tasks.

The Guide: Building Your Sync Engine

Step 1: Project Setup and Dependencies

First things first, let’s get our environment set up. I’ll skip the standard virtualenv setup since you likely have your own workflow for that. The key is to isolate our project’s dependencies.

You’ll need two main Python libraries: one for the Todoist API and another to execute AppleScript from Python. In your project directory, you’ll want to install `todoist-api-python` and `py-applescript`. You can do this with pip, for example: `pip install todoist-api-python py-applescript python-dotenv`.

We also need a place to store our API key securely. I recommend a simple `config.env` file in your project root. Just create a file with that name and add one line:

`TODOIST_API_KEY=”YOUR_API_KEY_HERE”`

We’ll use the `python-dotenv` library to load this key into our script securely, keeping it out of our source code.

Step 2: Get Your Todoist API Token

This is straightforward. Log in to your Todoist account on the web, go to Settings > Integrations > Developer, and you’ll find your API token there. Copy it and place it inside the quotes in your `config.env` file.

Pro Tip: I highly recommend creating a dedicated project in Todoist (e.g., “Siri Sync”) and a corresponding list in Apple Reminders. This contains the sync process, preventing the script from accidentally modifying your other critical projects.

Step 3: The Python Script – Initialization and Connections

Let’s start building our script. We’ll call it `sync_tasks.py`. The first part is all about setting up our connections and defining constants. We’ll load the API key, initialize the Todoist API client, and specify the names of the list and project we want to sync.


import os
import uuid
import applescript
from dotenv import load_dotenv
from todoist_api_python.api import TodoistAPI

# --- Configuration ---
load_dotenv('config.env')
TODOIST_API_KEY = os.getenv("TODOIST_API_KEY")
TODOIST_PROJECT_NAME = "Siri Sync" # The project in Todoist
REMINDERS_LIST_NAME = "Siri Sync" # The list in Apple Reminders
SYNC_ID_TAG = "sync_id:"

# --- API Initialization ---
api = TodoistAPI(TODOIST_API_KEY)

def main():
    print("Starting sync process...")
    # We will build out the logic here
    print("Sync process complete.")

if __name__ == "__main__":
    main()

The logic here is simple: we load our secret key from the `config.env` file, define the names of our target containers, and create an instance of the Todoist API client. The `SYNC_ID_TAG` is critical; we’ll use it to embed a unique ID in each task’s notes/description to prevent creating duplicates.

Step 4: The Core Logic – Fetching and Syncing

This is where the magic happens. A robust two-way sync is all about state management. We need to know which tasks are already linked. Our `SYNC_ID_TAG` will be our source of truth.

The strategy is:
1. Fetch all tasks from both Reminders and Todoist.
2. Map them out using their unique sync IDs.
3. Compare the lists to find new, un-synced items.
4. Create the missing items in the other service, adding the sync ID.
5. Check for status changes (e.g., completed tasks) and mirror them.

Here’s a simplified implementation focusing on syncing new tasks from Reminders to Todoist to illustrate the concept.


# (Add this inside your sync_tasks.py)

def get_todoist_project_id(project_name):
    try:
        projects = api.get_projects()
        for project in projects:
            if project.name == project_name:
                return project.id
    except Exception as e:
        print(f"Error finding Todoist project: {e}")
    return None

def get_existing_todoist_sync_ids(project_id):
    """Fetch all sync IDs from tasks in the target Todoist project."""
    sync_ids = set()
    try:
        tasks = api.get_tasks(project_id=project_id)
        for task in tasks:
            if task.description and SYNC_ID_TAG in task.description:
                # Extract the ID from the description
                for line in task.description.splitlines():
                    if line.startswith(SYNC_ID_TAG):
                        sync_ids.add(line.strip())
    except Exception as e:
        print(f"Error fetching Todoist tasks: {e}")
    return sync_ids

def sync_reminders_to_todoist(project_id, existing_sync_ids):
    """Find new Reminders and create them in Todoist."""
    # This is an AppleScript to get reminders that are not yet completed
    # It returns a list of "name|||notes" strings
    scpt = f'''
    tell application "Reminders"
        set reminder_list to list "{REMINDERS_LIST_NAME}"
        set output to ""
        repeat with r in (reminders of reminder_list whose completed is false)
            set r_name to name of r
            set r_notes to body of r
            set output to output & r_name & "|||" & r_notes & "\n"
        end repeat
        return output
    end tell
    '''
    
    result = applescript.run(scpt)
    if not result.out:
        print("No active reminders found to sync.")
        return

    reminders_to_sync = result.out.strip().split('\n')
    
    for item in reminders_to_sync:
        name, notes = item.split("|||", 1)
        
        if SYNC_ID_TAG in notes:
            # This reminder is already synced, so we skip it
            continue

        # This is a new reminder! Let's sync it.
        print(f"New reminder found: '{name}'. Syncing to Todoist.")
        new_sync_id = f"{SYNC_ID_TAG}{uuid.uuid4()}"
        
        try:
            # Create the task in Todoist with the sync ID in the description
            api.add_task(
                content=name,
                project_id=project_id,
                description=f"{notes}\n{new_sync_id}"
            )
            
            # Now, update the original Apple Reminder with the same sync ID
            update_scpt = f'''
            tell application "Reminders"
                set r_list to list "{REMINDERS_LIST_NAME}"
                set r_to_update to (first reminder of r_list where name is "{name}")
                set body of r_to_update to "{notes}\n{new_sync_id}"
            end tell
            '''
            applescript.run(update_scpt)
            print(f"Successfully synced '{name}'.")

        except Exception as e:
            print(f"Failed to sync reminder '{name}': {e}")


# --- Update your main function ---
def main():
    print("Starting sync process...")
    project_id = get_todoist_project_id(TODOIST_PROJECT_NAME)
    if not project_id:
        print(f"Todoist project '{TODOIST_PROJECT_NAME}' not found. Please create it.")
        return

    existing_ids = get_existing_todoist_sync_ids(project_id)
    sync_reminders_to_todoist(project_id, existing_ids)
    
    # You would build the reverse sync (Todoist -> Reminders) here
    # and the logic for checking completed status.

    print("Sync process complete.")

This code block demonstrates the core principle for a one-way sync. A full two-way implementation would also include a `sync_todoist_to_reminders` function and logic to handle updates for completed tasks, but this is the foundation you build everything on.

Pro Tip: Error handling is paramount. In my production setups, I wrap API calls in `try…except` blocks and log any failures. The Todoist API might be down, or an AppleScript could fail. Your script should be resilient to these transient issues.

Step 5: Scheduling the Sync

The final piece is automation. We need this script to run on a schedule. On macOS, the classic tool for this is `cron`. You can set up a cron job to execute the script every 30 minutes, every hour, or whatever interval you prefer.

A cron job definition for running this script once a day might look like this:

`0 2 * * * python3 /path/to/your/project/sync_tasks.py`

Remember to use the full path to your python executable and script. I’ve found that running it every 15-30 minutes provides a good balance between responsiveness and not hitting API rate limits.

Common Pitfalls

I’ve made a few mistakes setting this up in the past. Here’s what to watch out for:

  • Infinite Loops: Without a solid unique ID system, you can easily create a scenario where Service A creates a task in Service B, which the script then sees as a “new” task and tries to create back in Service A. The `sync_id` is your best defense against this.
  • Rate Limiting: Don’t run your script too frequently. Both AppleScript and the Todoist API can be slow or have limits. A sync every minute is overkill and will likely get you temporarily blocked.
  • Handling Duplicates: What if you have two reminders with the exact same name? My script’s update logic (using `first reminder where name is…`) is a bit naive. A more robust version would fetch reminders by their unique AppleScript ID.
  • State Mismatches: If the script fails halfway through (e.g., it creates the Todoist task but fails to update the Apple Reminder with the `sync_id`), you’ll have an orphaned task. Building in idempotency and state-checking is the advanced next step.

Conclusion

There you have it—a solid foundation for a two-way sync between Apple Reminders and Todoist. This setup bridges the gap between quick voice capture and structured project management beautifully. It automates a tedious manual process, reduces the chance of forgetting tasks, and ultimately streamlines your workflow.

While this guide provides the core logic, feel free to expand on it. You could add logic to sync due dates, priorities, or handle task deletions. The key is the sync ID pattern, which makes the whole system reliable. Happy automating

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ What are the core technologies used for this two-way sync?

The sync leverages Python 3.8+ with `todoist-api-python` for Todoist interaction and `py-applescript` for programmatic control over Apple Reminders on macOS, along with `python-dotenv` for secure API key management.

âť“ What are the advantages of this custom sync solution?

This custom script offers precise control over the synchronization logic, allowing for a robust two-way sync with unique ID management (`SYNC_ID_TAG`) to prevent duplicates and tailor the workflow specifically to user needs, which generic integrations might not fully support.

âť“ What is a common implementation pitfall and its solution?

A common pitfall is creating infinite loops where tasks are endlessly duplicated between services. This is addressed by using a `SYNC_ID_TAG` embedded in task descriptions to uniquely identify and track already synchronized items, preventing re-creation.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading