Dictate a task to Siri, get it dropped in the Notion Tasks DB with Status=Next, then enriched async by an LLM that fills in Area/Energy/Status/Project/Due Date/URL. Siri gets a fast 200 back; enrichment runs after.

Sister workflow to Project Capture — same architecture, same credentials, same conventions.

Quick Reference

ItemValue
n8n workflow IDuaQCrtTUYl8g4saD
Workflow nameTodo Capture (Siri → Notion + LLM enrichment)
Webhook URLhttps://n8n.cloud.mdbook.one/webhook/todo-capture
MethodPOST
LLM backendLibreChat agent (Qwen3.5:9b) via lmChatOpenAi
LibreChat agentagent_h2M-pQF66cfp0SCK9Vy5S
LLM prompt (git)mikayla/scripts:prompts/todo-capture.md
Notion targetTasks DB → Status=Next
StatusACTIVE — live in n8n (creds wired, webhook authed)

Architecture

Split-response pattern — Siri doesn’t wait for the LLM.

Webhook (Siri POST)
  ↓
Create Task                (Notion: name only, Status=Next)
  ↓
Respond to Siri            (200 immediately)
  ↓
Fetch Active Projects      (Notion: Projects DB where Status=Active)
  ↓
Build Enrichment Prompt    (assembles user-message: date + title + project list; rules live on the agent)
  ↓
Enrich w/ LLM              (chainLlm + lmChatOpenAi → LibreChat)
  ↓
Parse + Build Update       (reads $json.text, validates, builds Notion patch)
  ↓
Update Task w/ Enrichment  (Notion: patches Area/Energy/Status/Project/Due/URL/Notes,
                            or sets Needs Review=true on failure)

No inbox triage step. Every task lands with a real Status the moment it’s captured.

iOS Shortcut Spec

POST to the webhook with:

  • Header: Authorization: <bearer-or-shared-secret> — placeholder, fill in your own
  • Header: Content-Type: application/json
  • Body: { "title": "<dictated text>" }

Raw dictation goes in as-is — the LLM splits it into clean title + URL + notes.

LLM Enrichment

Uses LangChain pattern (same as the YouTube Summary workflow):

  • @n8n/n8n-nodes-langchain.chainLlm — main reasoning node
  • @n8n/n8n-nodes-langchain.lmChatOpenAi attached via ai_languageModel — model node
  • openAiApi credential points at LibreChat (chat.mdbook.me), agent does the actual model selection
  • Output is read as $json.text in the Parse node

The full classifier system prompt lives in git at mikayla/scripts:prompts/todo-capture.md. Source of truth for the running prompt is still the LibreChat agent UI — keep them in sync by hand for now.

Classifier output schema

{
  "title": "cleaned task name",
  "url": "extracted URL or null",
  "notes": "extra context or null",
  "areas": ["Homelab", "Code", ...],
  "energy": "Quick" | "Medium" | "Long" | "Deep",
  "status": "Next" | "Someday" | "Waiting",
  "project_id": "Notion page ID or null",
  "due_date": "YYYY-MM-DD or null"
}

Rules baked into the prompt

  • Energy buckets: Quick (<15m), Medium (15m–1hr), Long (1–3hr), Deep (3hr+)
  • Status: Next (default — actionable now or soon), Someday (explicit someday/maybe/eventually language), Waiting (dictation explicitly mentions waiting on someone or something external). Doing, Done, Cancelled are never LLM-settable on a freshly captured task.
  • Areas: Homelab / Code / Coaching / Teaching / Audio / Personal / Health / Errands / Music
  • Project match is strict — no fuzzy guessing, return null if unsure
  • Due dates ISO YYYY-MM-DD only, from explicit refs in title; vague refs → null
  • Today’s date injected by n8n in America/New_York; Parse node validates ISO before sending to Notion

Credentials Needed

  1. Notion internal integration token — must have access to the 🎯 Tasks parent page (covers Tasks + Projects DBs)
  2. HTTP Header Auth credential — for the inbound webhook Authorization check
  3. OpenAI API credential pointing at LibreChat — can reuse LibreChat- Youtube Summary cred or make a dedicated one for the todo enricher agent

All placeholders in n8n — never paste real tokens into LLM chat windows.

Schema (Tasks DB)

Status select options (Inbox dropped 2026-05-15): Next, Doing, Waiting, Someday, Done, Cancelled. Initial Create Task node forces Status=Next so the task lands on the active board immediately; the LLM can demote to Someday or Waiting via enrichment.

  • Needs Review — checkbox property, set to true when LLM enrichment fails or output is invalid
  • ⚠️ Needs Review — view filtered to Needs Review = true for triage
  • ⚠️ No Status — view filtered to Status IS EMPTY; safety net for any task that somehow lands without a status (should always be empty)

Gotchas

  • Database ID vs Data source ID. Notion’s public REST API wants the URL-style database ID. The collection:// data source ID is for internal MCP/native nodes only. Wrong one → 404 “resource not found.” n8n HTTP nodes hit the public API, so use the database ID.
  • URL property key. This workflow hits the raw Notion REST API, so the Parse node writes the field under its literal property name URL (properties.URL = { url }). The userDefined:URL prefix is a Notion MCP convention only — it does not apply to these HTTP-node calls. Don’t add it here.
  • LangChain output shape. chainLlm returns the model text on $json.text, NOT $json.output or $json.message.content. Easy to get wrong if you’re copying from a regular HTTP-call pattern.
  • Schema is load-bearing. If you change field names, types, or value enums in the classifier prompt, also update the Parse + Build Update node in n8n. Validation failure → Needs Review=true.
  • Default status is Next in n8n, not in the LLM. The initial Create Task node forces Status=Next so the task lands on the Now Board immediately. The LLM can later demote it to Someday/Waiting via the enrichment patch. Doing/Done/Cancelled are never LLM-settable.
  • Failure mode. If the LLM returns malformed JSON or any required field fails validation, the Parse node leaves the placeholder Status=Next in place, sets Needs Review=true, and skips the rest of the patch. Manual triage via the Needs Review view.
  • Timezone. All date math runs in America/New_York, injected from n8n’s current time, not the LLM’s notion of “today.”

Disabling

n8n UI → workflow uaQCrtTUYl8g4saD → toggle Active off. Or delete the webhook credential to break it at the auth layer without touching the workflow itself.

Changelog

  • 2026-06-03 — Doc refresh: workflow confirmed active in n8n (Quick Reference previously said INACTIVE). Corrected the URL-key gotcha — the raw REST node uses the literal URL key, not the userDefined:URL MCP form. Noted the Parse node is on the v3 output schema (adds cleaned title, url, due_date on top of the v2 status field). Fixed the sister-workflow link to the Quartz path.
  • 2026-05-15 — Retired the Inbox triage step. Inbox Status option dropped from the Tasks DB; 📥 Inbox view repurposed as ⚠️ No Status (safety net for tasks without a status). n8n now creates tasks with Status=Next on the initial node, mirroring Project Capture. LLM prompt v2 adds a status field (Next/Someday/Waiting) so the enricher can demote when the dictation calls for it.