The Smart Schedule feature was a valuable asset in Todoist that helped many users efficiently manage their backlog of tasks. Todoist removed it, labeling it as 'overly complex' and not widely used.
To recreate the benefits of Smart Schedule, this project distributes undated tasks (Todoist's no date filter) across the upcoming week, biasing toward days that already have fewer tasks scheduled. The scheduler honors your Todoist start_day setting (Monday vs Sunday vs anything else), so the "upcoming week" lines up with how you already think about your week.
The scheduler runs every Sunday at 21:00 UTC as an AWS Lambda invoked by EventBridge.
All undated tasks land in the upcoming week, no matter how many there are — there's no overflow to subsequent weeks in this mode. Each task is assigned to whichever day currently has the fewest tasks (a min-heap), so the week ends up roughly balanced. Task order is preserved as-is from the API.
To spill overflow into subsequent weeks, set MAX_TASKS_PER_DAY (see below).
Set the MAX_TASKS_PER_DAY environment variable to limit how many total tasks any single day can hold. Existing tasks already on the calendar count toward this limit.
The distribution follows these rules:
- Sort by age. All undated tasks are sorted oldest-first by creation date.
- Fill the upcoming week, balanced and front-loaded. Tasks are assigned to days in chronological order (Monday first, then Tuesday, etc.). Each day gets roughly the same number of new tasks — at most one more than any other day. When the week has enough capacity to hold all tasks, they spread evenly (e.g. 5 tasks across 7 days = one per day for 5 days). When the week is full, each day fills to the cap.
- Oldest tasks land on the earliest days. Because tasks are sorted oldest-first and days are filled in order, the oldest tasks always end up on the earliest days of the week.
- Overflow to the next week. If the week's capacity (
maxPerDay × 7minus existing tasks) isn't enough, the remaining (newest) tasks spill into the following week. The same balanced distribution applies there. This continues week by week until every task is placed. - Skip full days. If a day already has
maxPerDayor more tasks, it gets zero new tasks and doesn't affect the balance calculation.
Examples (week starts Monday, cap = 5, empty calendar):
| Undated tasks | Mon | Tue | Wed | Thu | Fri | Sat | Sun | Overflow |
|---|---|---|---|---|---|---|---|---|
| 5 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | none |
| 10 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | none |
| 35 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | none |
| 40 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 5 newest → week 2 |
With existing tasks: if Monday already has 3 tasks and the cap is 5, Monday can absorb 2 more. The rest of the week absorbs the remainder, balanced as above.
Configuration:
For local runs:
MAX_TASKS_PER_DAY=5 TODOIST_API_KEY=... npm run schedulerFor the deployed Lambda, pass it as a CloudFormation parameter:
sam deploy --parameter-overrides MaxTasksPerDay=5- TypeScript (Node 24, ES2024) on AWS Lambda (
arm64) - AWS SAM for infrastructure (
aws/template.yaml) - SSM Parameter Store for the Todoist API token
- CloudWatch alarms wired to shared-infra for error notifications
- Vitest + Biome + native git hooks (
.git-hooks/) for local quality gates
npm install
echo 'TODOIST_API_KEY=...' > .env.local # only needed if you want to run the CLI
npm test # vitest
npm run check:ts # tsc --noEmit
npx biome ci . # lint/formatTo run the scheduler against your real Todoist account from the terminal:
TODOIST_API_KEY=... npm run scheduler-
Store the Todoist API key in SSM (one-time):
aws ssm put-parameter \ --name /todoist-backlog-scheduler/api-key \ --type SecureString \ --value '<your-api-key>' -
Build and deploy:
npm run deploy:infra # sam build && sam deployFirst-time deploys: copy
samconfig.toml.example→samconfig.toml(gitignored), or runsam deploy --guided. SetAWS_PROFILElocally for SSO.
The Lambda runs every Sunday at 21:00 UTC. CloudWatch logs are retained for 30 days. Errors fan out to john@jsolly.com via the shared-infra project.