Process emails, chat messages, meeting recaps, and forge issues/MRs into todos and maintain To Do.md — archives completed items, flags overdue, deduplicates against existing todos. Run manually with /inbox-process or automatically via hourly LaunchAgent.
Manage the todo list at VAULT/To Do.md by processing emails, chat messages, meeting recaps, forge issues/MRs, and performing maintenance.
All paths, project mappings, and user identity come from config.yaml at the repo root. Read it at the start of every run.
# Find config.yaml — follow the symlink from the skill dir to the repo
REPO_ROOT=$(cd "$(dirname "$(readlink ~/.claude/skills/inbox-process)")" && cd ../.. && pwd)
Key config values used by this skill:
vault.root + vault.todo_file → To Do.md pathvault.root + vault.archive_file → archive pathuser.name_variants → meeting action item ownership matchinguser.forge_username (or legacy user.gitlab_username) → forge queriesprojects.* → keyword mapping, forge paths, todo sectionsstate_file → processed item trackingproviders → active provider configuration per type (email, chat, forge, etc.)/inbox-process — run all jobs (email + chat + recaps + forge + tidy)/inbox-process email — only email processing/inbox-process chat — only chat message processing/inbox-process recaps — only meeting recap processing/inbox-process forge — only forge issue/MR processing/inbox-process tidy — only run todo maintenance/inbox-process backfill — 30-day scan of email, chat, recaps + forgeRun jobs in this order: Email Processing then Chat Processing then Meeting Recap Processing then Forge Processing then Todo Maintenance.
Step 1: Read emails via provider dispatch
Run the email provider dispatch to fetch emails from all active email providers:
# Normal run: last 24 hours
providers/_dispatch.sh email 1
# Backfill run: last 30 days
providers/_dispatch.sh email 30
The dispatch returns a merged JSON array from all active email providers (e.g., Outlook, Gmail, IMAP). Each email object has: id, provider, subject, sender, date, read, flagged, body, webUrl.
The provider field indicates which provider sourced the email (e.g., outlook, gmail).
Each provider handles its own filtering (processed markers, encrypted emails, CUI content). The dispatch merges results and outputs a single JSON array to stdout.
If the dispatch returns [] or fails, skip to Job 1.5.
Step 2: Read the current To Do.md
Read the To Do.md file (path from vault.root + vault.todo_file in config.yaml) to get all existing todo items for deduplication.
Step 3: Analyze each email for actionable items
For each email, determine if it contains actionable items:
For each actionable item found, extract:
projects section of config.yaml. For each project, check if any of its keywords appear in the email subject or body. If a project has exclude_keywords, skip it even if other keywords match. If no project matches, omit the project tag (item goes under Misc).🛫 YYYY-MM-DD. Always include this.📅 YYYY-MM-DD). If no date, omit.🔺 high — "urgent", "ASAP", "critical", "by end of day", explicit escalation, senior leadership request🔼 medium — default when priority is unclear from the messageStep 4: Deduplicate against existing todos
Check the state file FIRST. If the email ID is already in processed_email_{provider}_ids (e.g., processed_email_outlook_ids, processed_email_gmail_ids), skip it entirely — do not analyze or create a todo. Only process emails whose IDs are NOT in the processed list.
Backward compatibility: Also check the legacy key processed_ids — if an ID exists there, treat it as processed.
For emails not yet in the state file, fuzzy-match the new item against all items in To Do.md (both checked and unchecked) to avoid duplicates:
Step 5: Add new todos to To Do.md
Insert new items under the appropriate project section:
#project/acme goes under ## ACME)## MiscFormat (note the priority, start date, due date, source tag, and metadata sub-item):
- [ ] 🔼 Description #project/name #source/{provider} 🛫 YYYY-MM-DD 📅 YYYY-MM-DD
- *From: sender@email | YYYY-MM-DD | [view](url)*
The #source/{provider} tag uses the provider field from the email JSON (e.g., #source/outlook, #source/gmail).
Priority goes right after [ ]. Start date (🛫) is always the email send date. Due date (📅) only if a deadline was mentioned.
Step 6: Mark emails as processed
Record the email ID in the state file under the provider-namespaced key processed_email_{provider}_ids (e.g., processed_email_outlook_ids). This prevents re-processing on the next run.
Individual providers may have their own mark-as-processed mechanisms (e.g., Outlook uses a mark-queue file). The dispatch handles this internally — the skill only needs to update the state file.
Read the state file at the start of each run and skip any IDs listed there.
Step 1: Read chat messages via provider dispatch
Run the chat provider dispatch to fetch messages from all active chat providers:
# Normal run: last 24 hours
providers/_dispatch.sh chat 1
# Backfill run: last 30 days
providers/_dispatch.sh chat 30
The dispatch returns a merged JSON array from all active chat providers (e.g., Teams, Slack). Each message has: id, provider, sender, senderEmail, date, body, chatId, messageId, webUrl.
Each provider handles its own source-specific logic (LevelDB parsing, API calls, HTML stripping, CUI filtering). The dispatch merges and deduplicates results.
If the dispatch returns [] or fails, skip to Job 2.
Step 2: Check state file
Read the state file and skip any message IDs in processed_chat_{provider}_ids (e.g., processed_chat_teams_ids, processed_chat_slack_ids). This check is absolute — if an ID is in the state file, skip it entirely without further analysis.
Backward compatibility: Also check the legacy key processed_teams_ids.
Step 3: Analyze messages for actionable items
Teams messages are conversational fragments, NOT structured requests like email. Be significantly more conservative than email analysis.
Before creating any todo from Teams:
Apply the same project keyword mapping and priority inference as email for items that pass the above filters. Start date is the message's send date.
Step 4: Deduplicate against existing todos
For messages not already skipped by the state file check (Step 2), fuzzy-match the new item against all items in To Do.md (both checked and unchecked) to avoid duplicates. Checking against checked items catches actions the user has already completed but not yet archived.
Step 5: Add new todos to To Do.md
Format:
- [ ] 🔼 Description #project/name #source/{provider} 🛫 YYYY-MM-DD 📅 YYYY-MM-DD
- *From: Sender Name (sender@email) | YYYY-MM-DD | [view](url)*
The #source/{provider} tag uses the provider field from the message JSON (e.g., #source/teams, #source/slack).
If senderEmail is empty, omit the parenthetical. If webUrl is empty, omit the link. Start date is always the message send date. Due date only if a deadline was mentioned.
Step 6: Update state file
Add processed message IDs to processed_chat_{provider}_ids in the state file (e.g., processed_chat_teams_ids).
Step 1: Read meeting recaps via provider dispatch
Run the meeting-recap provider dispatch:
# Normal run: last 24 hours
providers/_dispatch.sh meeting-recap 1
# Backfill run: last 30 days
providers/_dispatch.sh meeting-recap 30
The dispatch returns a merged JSON array from all active meeting-recap providers. Each action item has: id, provider, callId, meetingTitle, meetingDate, actionTitle, description, assignedTo.
Each provider handles its own source-specific logic (LevelDB parsing, API calls, name variant matching, CUI filtering). The dispatch merges results.
If the dispatch returns [] or fails, skip to Job 2.
Step 2: Check state file
Read the state file and skip any IDs in processed_recap_{provider}_ids (e.g., processed_recap_teams_ids). This check is absolute — if an ID is in the state file, skip it entirely without further analysis.
Backward compatibility: Also check the legacy key processed_recap_ids.
Step 3: Analyze action items
Meeting recap action items are already structured and pre-analyzed by Microsoft's AI. Trust the ownership assignment. Use the description field directly as the todo text. Infer #project/ tags from the meeting title and action item content using the same keyword mapping as email/Teams.
Priority: Infer from the action item description and context. Items with deadlines or blocking language get 🔺 high. Routine follow-ups get 🔼 medium (default).
Step 4: Deduplicate against existing todos
For action items not already skipped by the state file check (Step 2), fuzzy-match the new item against all items in To Do.md (both checked and unchecked). Meeting recap items often overlap with requests already made via email or Teams — checking both checked and unchecked items prevents re-creation of completed work.
Step 5: Add new todos to To Do.md
Format:
- [ ] 🔼 Description #project/name #source/meeting-recap 🛫 YYYY-MM-DD
- *From: Meeting Title | YYYY-MM-DD | assigned to: Name*
Start date (🛫) is the meeting date. Due date (📅) only if the action item mentions a specific deadline.
Step 6: Update state file
Add processed action item IDs to processed_recap_{provider}_ids in the state file (e.g., processed_recap_teams_ids).
Step 1: Read forge issues and MRs via provider dispatch
Run the forge provider dispatch:
providers/_dispatch.sh forge
The dispatch returns a merged JSON array from all active forge providers (e.g., GitLab, GitHub, Codeberg). Each item has: id, provider, type, title, project, dueDate, milestone, webUrl, author, labels.
Types: issue (assigned to user), mr (authored by user), mr-review (user is reviewer).
If the dispatch returns [] or fails, skip to Job 2.
Step 2: Check state file
Read the state file and note which IDs are in processed_forge_{provider}_ids (e.g., processed_forge_gitlab_ids, processed_forge_github_ids). This check is absolute — if an ID is already processed, skip it entirely. Do not re-analyze, re-enrich, or re-create a todo for it.
Backward compatibility: Also check the legacy key processed_gitlab_ids.
Step 3: Process each item
For each GitLab item:
Infer project tag from the forge project path:
projects.*.forge_paths (or legacy projects.*.gitlab_paths) in config.yamlDetermine due date:
dueDate field directly (already resolved: issue date > milestone date > empty)Construct todo text:
Deduplicate with enrichment:
To Do.md (both checked and unchecked)#source/{provider} tag (e.g., #source/gitlab, #source/github) and a metadata sub-item with the forge reference/link to the EXISTING todo. Do NOT create a duplicate.Step 4: Add new todos / enrich existing
New todo format:
- [ ] 🔼 Title #project/name #source/{provider} 🛫 YYYY-MM-DD 📅 YYYY-MM-DD
- *From: project/repo#iid | milestone: Name | [view](url)*
The #source/{provider} tag uses the provider field from the JSON (e.g., #source/gitlab, #source/github).
Start date (🛫) is the issue's created_at date. Due date (📅) from issue/milestone due date. Priority: infer from labels (e.g., Priority::High → 🔺), milestone urgency, or default to 🔼 medium.
MR format:
- [ ] 🔼 MR: Title #project/name #source/{provider} 🛫 YYYY-MM-DD
- *From: project/repo!iid | [view](url)*
Review MR format:
- [ ] 🔺 Review MR: Title #project/name #source/{provider} 🛫 YYYY-MM-DD
- *From: project/repo!iid | author: username | [view](url)*
Review MRs default to 🔺 high — someone is waiting on your review.
Enrichment format (added to existing todo):
- *From: project/repo#iid | milestone: Name | [view](url)*
Step 5: Update state file
Add processed item IDs to processed_forge_{provider}_ids in the state file (e.g., processed_forge_gitlab_ids).
Run the deterministic tidy script. This is NOT an LLM task — the script handles all maintenance logic directly:
python3 scripts/tidy.py
The tidy script reads config.yaml to find the To Do.md and archive file paths, then performs:
- [x] items to the archive, grouped by completion date (✅ YYYY-MM-DD)## Overdue section with items past their 📅 due date🔼 to 🔺 for items due within 2 days or overdue > 14 daysAuto email-draft: After tidy completes, check if any of the archived items have a #source/outlook or #source/gmail tag (any email-sourced tag). For each one, run the email-draft provider dispatch to create a reply draft:
providers/_dispatch.sh draft "recipient" "Re: Subject" "body"
This opens a draft in the user's email client for review. The user reviews before sending. Skip if no email-sourced items were archived.
Important: When archiving completed items, do NOT remove their IDs from the state file. The state file tracks what has been processed, not what is currently active in To Do.md. Removing IDs from the state file would cause re-processing on the next run, re-creating todos the user already completed.
To Do.md and the archive