Corky logo

Corky

Alpha software. Expect breaking changes between minor versions. See the changelog for migration notes.

Corky consolidates conversations from multiple email accounts into a single flat directory of Markdown files. Draft replies with AI assistance. Share scoped threads with collaborators via git.

Corky syncs threads from any IMAP provider (Gmail, Protonmail Bridge, self-hosted) into mail/conversations/ — one file per thread, regardless of source. A thread that arrives via both Gmail and Protonmail merges into one file. Labels, accounts, and contacts are metadata, not directory structure. Slack and social media sources are planned.

Why Corky?

Most AI email tools require OAuth access to your entire account. Once authorized, the agent can read every message, every contact, every thread — and you're trusting the service not to overreach.

Corky inverts this:

  1. You label threads in your email client. Only threads you explicitly label get synced locally.
  2. Labels route to scoped views. Each mailbox gives a collaborator or agent a directory containing only the threads labeled for them — nothing else.
  3. Credentials never leave your machine. Config lives inside mail/ (your private data repo). Agents draft replies in markdown; only you can push to your email.
  4. Reduced context poisoning. Agents only see the threads you route to them — not your entire inbox. A focused context means fewer irrelevant details leaking into prompts, better signal-to-noise, and more accurate replies.
  5. Per-contact context. Each mailbox ships with AGENTS.md (or CLAUDE.md), voice.md, and relationship-specific instructions — so agents adapt their tone and knowledge to each contact automatically.

Designed for humans and agents

Corky is built around files, CLI commands, and git — interfaces that work equally well for humans and AI agents. No GUIs, no OAuth popups, no interactive prompts.

  • Everything is files. Threads are Markdown. Config is TOML. Drafts are Markdown.
  • CLI is the interface. Every operation is a single corky command. Scriptable and composable.
  • Single-binary install. One curl | sh gives collaborators corky unanswered and corky draft validate.
  • Self-documenting repos. Each shared repo ships with AGENTS.md, voice.md, and a README.md.

Tech Stack

  • Language: Rust (2021 edition)
  • CLI: clap (derive macros)
  • Serialization: serde + toml / toml_edit / serde_json
  • IMAP: imap + native-tls
  • Email parsing: mailparse
  • SMTP: lettre
  • Dates: chrono
  • Storage: Markdown files (one flat directory, one file per conversation thread)
  • Sources: Any IMAP provider (Gmail, Protonmail Bridge, generic IMAP); Slack and social media planned

Installation

pip / pipx (all platforms)

pip install corky
# or
pipx install corky

This installs a prebuilt wheel with the compiled binary — no Rust toolchain needed.

Shell installer (Linux & macOS)

curl -sSf https://raw.githubusercontent.com/btakita/corky/main/install.sh | sh

This downloads a prebuilt binary to ~/.local/bin/corky. Use --system to install to /usr/local/bin instead (requires sudo).

From source

cargo install --path .

Windows

pip install corky is the easiest option. Alternatively, download .zip from GitHub Releases or build from source with cargo install --path ..

Quick Start

Initialize

corky init --user you@gmail.com

This creates ~/Documents/mail with directory structure, .corky.toml, and empty config files inside it.

Configure

Edit mail/.corky.toml with your email credentials:

[accounts.personal]
provider = "gmail"
user = "you@gmail.com"
password_cmd = "pass email/personal"
labels = ["correspondence"]
default = true

Sync

corky sync

Threads are written to mail/conversations/[slug].md — one file per thread. Labels and accounts are metadata inside each file. A manifest.toml index is generated after each sync.

Basic workflow

corky sync                          # Pull new threads
corky unanswered                    # See what needs a reply
# Draft a reply in mail/drafts/
corky draft validate mail/drafts/FILE.md   # Check format
corky draft push mail/drafts/FILE.md       # Save as email draft

Configuration

All config lives inside the data directory (mail/).

.corky.toml

The main configuration file at mail/.corky.toml:

[owner]
github_user = "username"
name = "Display Name"

[accounts.personal]
provider = "gmail"                      # gmail | protonmail-bridge | imap
user = "you@gmail.com"
password = ""                           # Inline password (not recommended)
password_cmd = ""                       # Shell command to retrieve password
labels = ["correspondence"]
imap_host = ""                          # Auto-filled by provider preset
imap_port = 993
imap_starttls = false
smtp_host = ""
smtp_port = 465
drafts_folder = "Drafts"
sync_days = 3650                        # How far back to sync
default = false                         # Mark one account as default

[contacts.alex]
emails = ["alex@example.com"]
labels = ["correspondence"]
account = "personal"

[routing]
for-alex = ["mailboxes/alex"]
shared = ["mailboxes/alice", "mailboxes/bob"]

[mailboxes.alex]
auto_send = false

[watch]
poll_interval = 300                     # Seconds between polls
notify = false                          # Desktop notifications

Account providers

Provider presets fill in IMAP/SMTP connection defaults:

Fieldgmailprotonmail-bridgeimap (generic)
imap_hostimap.gmail.com127.0.0.1(required)
imap_port9931143993
imap_starttlsfalsetruefalse
smtp_hostsmtp.gmail.com127.0.0.1(required)
smtp_port4651025465
drafts_folder[Gmail]/DraftsDraftsDrafts

Any preset value can be overridden per-account.

Password resolution

  1. password field (inline string)
  2. password_cmd (shell command, captures stdout, strips trailing whitespace)
  3. Error if neither set

Label scoping

Use account:label syntax to bind a label to a specific account (e.g. "proton-dev:INBOX"). Plain labels match all accounts.

Data directory resolution

The data directory is resolved at runtime in this order:

  1. mail/ directory in current working directory
  2. CORKY_DATA environment variable
  3. App config mailbox (named mailboxes)
  4. ~/Documents/mail (fallback)

App config

Platform-specific location for named mailboxes:

  • Linux: ~/.config/corky/config.toml
  • macOS: ~/Library/Application Support/corky/config.toml
  • Windows: %APPDATA%/corky/config.toml
default_mailbox = "personal"

[mailboxes.personal]
path = "~/Documents/mail"

[mailboxes.work]
path = "~/work/mail"

Commands

All commands are available through the corky CLI.

General

corky --help                    # Show all commands
corky help [FILTER]             # Show command reference (optional filter)
corky init --user EMAIL        # Initialize in current directory
corky init --user EMAIL /path # Initialize at specific path
corky install-skill email     # Install the email agent skill

init

corky init --user EMAIL [PATH] [--provider PROVIDER]
           [--password-cmd CMD] [--labels LABEL,...] [--github-user USER]
           [--name NAME] [--mailbox-name NAME] [--sync] [--force]

Creates {path}/mail/ with directory structure, .corky.toml, and voice.md. If inside a git repo, adds mail to .gitignore. Registers the project dir as a named mailbox in app config.

  • --provider: gmail (default), protonmail-bridge, imap
  • --labels: default correspondence (comma-separated)
  • --force: overwrite existing config
  • --sync: run sync after init

install-skill

corky install-skill NAME

Install an agent skill into the current directory. Currently supported: email. Mailbox repos ship the skill automatically via mb add/mb reset.

Sync

corky sync                     # Incremental IMAP sync (all accounts)
corky sync full                # Full re-sync (ignore saved state)
corky sync account personal    # Sync one account
corky sync routes              # Apply routing rules to existing conversations
corky sync mailbox [NAME]      # Push/pull shared mailboxes

sync-auth

corky sync-auth

Gmail OAuth setup. Requires credentials.json from Google Cloud Console.

Import

Telegram

corky sync telegram-import FILE            # Import Telegram Desktop JSON export
corky sync telegram-import DIR             # Import from directory containing result.json
corky sync telegram-import FILE --label personal --account tg-personal

Import Telegram Desktop JSON exports into corky conversations. Each chat becomes a thread with ID tg:{chat_id}. Export from Telegram Desktop: Settings > Advanced > Export Telegram data > JSON format.

Slack

corky slack import FILE.zip                # Import Slack workspace export ZIP
corky slack import FILE.zip --label work --account slack-work

Import Slack workspace export ZIPs. Messages are grouped by thread_ts into threads with ID slack:{channel_id}:{thread_ts}. Export from Slack: Workspace admin > Settings > Import/Export Data > Export.

Both commands support --label (default: provider name) and --account (default: provider name) flags.

Email

corky list-folders [ACCOUNT]   # List IMAP folders for an account
corky add-label LABEL --account NAME         # Add a label to sync config
corky unanswered                             # Find threads awaiting a reply (all scopes)
corky unanswered .                           # Root conversations only
corky unanswered NAME                        # Specific mailbox only

Drafts

corky draft new "Subject" --to EMAIL         # Scaffold a new draft file
corky draft new "Subject" --to EMAIL --mailbox NAME  # Create in mailbox drafts/
corky draft validate                         # Validate all drafts (root + mailboxes)
corky draft validate .                       # Validate root drafts only
corky draft validate NAME                    # Validate drafts in a mailbox
corky draft validate FILE [FILE...]          # Validate specific files
corky draft push mail/drafts/FILE.md         # Save a draft via IMAP
corky draft push mail/drafts/FILE.md --send  # Send via SMTP

draft new

corky draft new SUBJECT --to EMAIL [--cc EMAIL] [--account NAME]
               [--from EMAIL] [--in-reply-to MSG-ID] [--mailbox NAME]

Scaffolds a new draft file with pre-filled metadata. Creates drafts/YYYY-MM-DD-slug.md and prints the path. Author resolved from [owner] name in .corky.toml.

Reply Threading Heuristics

When drafting an email, determine whether to thread as a reply or start a new thread:

User intentAction
Follow-up, correction, or reply to a recent emailThreaded reply — find the original in mail/conversations/, set in_reply_to to its Message-ID, derive subject as Re: <original subject>
New topic to the same personNew thread — no in_reply_to, fresh subject
AmbiguousDefault to reply if there's a recent conversation with the same contact; ask if unclear

Gmail threading requirements:

  • In-Reply-To and References headers must reference the original Message-ID
  • Subject must match (adding Re: prefix is fine; changing the subject entirely breaks threading)
  • Mismatched subjects cause Gmail to create a new thread even with correct headers

draft push

Default: creates a draft via IMAP APPEND to the drafts folder. --send: sends via SMTP. Requires Status to be review or approved. After sending, updates Status to sent.

Account resolution:

  1. **Account** field → match by name in .corky.toml
  2. **From** field → match by email address
  3. Fall back to default account
  4. Credential bubbling: if the draft is inside a mailbox, walk parent directories for a .corky.toml with matching credentials

Contacts

corky contact add NAME --email EMAIL [--email EMAIL2]   # Add a contact manually
corky contact add --from SLUG [NAME]                    # Create from a conversation
corky contact info NAME                                 # Show contact details + threads

contact add

Manual mode: creates mail/contacts/{name}/ with AGENTS.md template and CLAUDE.md symlink. Updates .corky.toml.

From-conversation mode (--from): finds the conversation, extracts non-owner participants from From/To/CC headers, and creates an enriched contact with pre-filled AGENTS.md (Topics, Formality, Tone, Research sections).

contact info

Aggregates contact information: emails from config, AGENTS.md content, matching threads from manifest.toml (root and mailboxes), and a summary with thread count and last activity.

Mailboxes

corky mailbox add NAME --label LABEL [--name NAME] [--github] [--pat]
corky mailbox sync [NAME]                   # Push/pull shared mailboxes
corky mailbox status                        # Check mailbox status
corky mailbox list                          # List registered mailboxes
corky mailbox remove NAME [--delete-repo]   # Remove a mailbox
corky mailbox rename OLD NEW [--rename-repo] # Rename a mailbox
corky mailbox reset [NAME] [--no-sync]      # Regenerate templates

All mailbox commands accept the mb alias (e.g. corky mb add).

Watch

corky watch                    # Poll IMAP and sync on an interval
corky watch --interval 60      # Override poll interval (seconds)

Global flags

corky --mailbox NAME <command>  # Use a specific mailbox for any command

Development

corky audit-docs               # Audit instruction files for staleness

File Formats

Directory layout

mail/
  conversations/          # One file per thread, all sources merged
    project-update.md     # Immutable slug filename
    lunch-plans.md        # mtime = last message date
  contacts/               # Per-contact context for drafting
    alex/
      AGENTS.md           # Relationship, tone, topics, notes
      CLAUDE.md -> AGENTS.md
  drafts/                 # Outgoing messages
  mailboxes/              # Named mailboxes
    {name}/
      conversations/
      drafts/
      contacts/
      AGENTS.md
      CLAUDE.md -> AGENTS.md
      README.md
      voice.md
  manifest.toml           # Thread index (generated by sync)
  .sync-state.json        # IMAP sync state
  .corky.toml             # Configuration
  voice.md                # Writing style guidelines

Conversation markdown

Each synced thread is a single Markdown file in conversations/:

# [Subject]

**Labels**: label1, label2
**Accounts**: account1, account2
**Thread ID**: thread key
**Last updated**: RFC 2822 date

---

## Sender Name <email@example.com> — Mon, 01 Jan 2024 12:00:00 +0000

Body text

---

## Reply Sender — Tue, 02 Jan 2024 09:00:00 +0000

Reply body

No subdirectories for accounts or labels. A conversation with the same person may arrive via Gmail, Protonmail, or both — it merges into one file. Source metadata is tracked inside each file.

Immutable filenames. Each thread gets a [slug].md name derived from the subject on first write. The filename never changes. Thread identity is tracked by **Thread ID** metadata.

File mtime is set to the last message date. ls -t sorts by thread activity.

Draft markdown

Drafts live in mail/drafts/ (private) or mail/mailboxes/{name}/drafts/ (collaborator). Filename convention: [YYYY-MM-DD]-[slug].md.

# [Subject]

**To**: recipient@example.com
**CC**: (optional)
**Status**: draft
**Author**: brian
**Account**: (optional — account name from .corky.toml)
**From**: (optional — email address)
**In-Reply-To**: (optional — message ID)

---

Draft body goes here.

Status values: draftreviewapprovedsent

manifest.toml

Generated after each sync. Indexes threads by subject, labels, accounts, contacts, and last-updated date.

[threads.project-update]
subject = "Project Update"
thread_id = "project update"
labels = ["correspondence"]
accounts = ["personal"]
last_updated = "Mon, 01 Jan 2024 12:00:00 +0000"
contacts = ["alex"]

.sync-state.json

Tracks IMAP UIDs per-account for incremental sync.

{
  "accounts": {
    "personal": {
      "labels": {
        "correspondence": {
          "uidvalidity": 12345,
          "last_uid": 67890
        }
      }
    }
  }
}

Mailboxes

Share specific email threads with people or AI agents via scoped directories or GitHub repos.

Adding a mailbox

# Plain directory (default)
corky mailbox add alex --label for-alex --name "Alex"

# With GitHub submodule
corky mailbox add alex --label for-alex --name "Alex" --github

# AI agent (uses a PAT instead of GitHub invite)
corky mailbox add assistant-bot --label for-assistant --pat

# Bind all labels to one account
corky mailbox add alex --label for-alex --account personal

This creates a scoped directory under mailboxes/{name}/. With --github, it also creates a private GitHub repo and adds it as a submodule.

Multiple mailboxes

Manage multiple correspondence directories (personal, work, etc.) with named mailboxes:

# Init registers a mailbox automatically
corky init --user you@gmail.com
corky init --user work@company.com ~/work/project --mailbox-name work

# List registered mailboxes
corky mailbox list

# Use a specific mailbox for any command
corky --mailbox work sync
corky --mailbox personal mailbox status

Mailboxes are stored in platform-specific app config. The first mailbox added becomes the default.

Routing

Labels in .corky.toml route to mailbox directories:

[routing]
for-alex = ["mailboxes/alex"]
shared = ["mailboxes/alice", "mailboxes/bob"]

One label can fan-out to multiple mailboxes. Use account:label syntax for per-account scoping.

Daily workflow

# 1. Sync emails — shared labels route to mailboxes/{name}/conversations/
corky sync

# 2. Push synced threads to mailbox repos & pull their drafts
corky mailbox sync

# 3. Check what's pending without pushing
corky mailbox status

# 4. Review a collaborator's draft and push it as an email draft
corky draft push mailboxes/alex/drafts/2026-02-19-reply.md

What collaborators can do

  • Read conversations labeled for them
  • Draft replies in mailboxes/{name}/drafts/ following the format in AGENTS.md
  • Run corky unanswered and corky draft validate in their repo
  • Push changes to their shared repo

What only you can do

  • Sync new emails (corky sync)
  • Push synced threads to mailbox repos (corky mailbox sync)
  • Send emails (corky draft push --send)
  • Change draft Status to sent

Managing mailboxes

corky mailbox remove alex                    # Remove a mailbox
corky mailbox remove alex --delete-repo      # Also delete GitHub repo
corky mailbox rename old-name new-name       # Rename a mailbox
corky mailbox reset [NAME]                   # Regenerate template files

Contacts

Per-contact directories give Claude context when drafting emails — relationship history, tone preferences, recurring topics.

Adding a contact

corky contact-add alex --email alex@example.com --email alex@work.com \
  --label correspondence --account personal

This creates mail/contacts/alex/ with an AGENTS.md template (+ CLAUDE.md symlink) and updates .corky.toml.

Contact context

Edit mail/contacts/{name}/AGENTS.md with:

  • Relationship: How you know this person, shared history
  • Tone: Communication style overrides (defaults to voice.md)
  • Topics: Recurring subjects, current projects
  • Notes: Freeform context — preferences, pending items, important dates

Contact metadata

Contact metadata in .corky.toml maps names to email addresses (for manifest tagging, not sync routing):

[contacts.alex]
emails = ["alex@example.com", "alex@work.com"]
labels = ["correspondence"]
account = "personal"

Watch Daemon

Run as a daemon to poll IMAP, sync threads, and push to shared repos automatically.

Usage

# Interactive — polls every 5 minutes (default), Ctrl-C to stop
corky watch

# Custom interval
corky watch --interval 60

Configuration

Configure in mail/.corky.toml:

[watch]
poll_interval = 300    # seconds between polls (default: 300)
notify = true          # desktop alerts on new messages (default: false)

CLI --interval overrides config.

Notifications

  • macOS: osascript -e 'display notification ...'
  • Linux: notify-send
  • Silently degrades if the notification tool is not installed.

Running as a system service

Linux (systemd)

cp services/corky-watch.service ~/.config/systemd/user/
# Edit WorkingDirectory in the unit file to match your setup
systemctl --user enable --now corky-watch
journalctl --user -u corky-watch -f   # view logs

macOS (launchd)

cp services/com.corky.watch.plist ~/Library/LaunchAgents/
# Edit WorkingDirectory in the plist to match your setup
launchctl load ~/Library/LaunchAgents/com.corky.watch.plist
tail -f /tmp/corky-watch.log          # view logs

Signals

SIGTERM and SIGINT trigger a clean shutdown — finish the current poll, then exit.

Sandboxing

Most AI email tools (OpenClaw, etc.) require OAuth access to your entire account. Once authorized, the agent can read every message, every contact, every thread — and you're trusting the service not to overreach.

Corky inverts this. You control what any agent or collaborator can see:

  1. You label threads in your email client. Only threads you explicitly label get synced locally.
  2. Labels route to scoped views. Each mailbox gives the collaborator/agent a directory containing only the threads labeled for them — nothing else.
  3. Credentials never leave your machine. Config lives inside mail/ (your private data repo). Agents draft replies in markdown; only you can push to your email.

Blast radius

An agent added with corky mailbox add assistant --label for-assistant can only see threads you've tagged for-assistant. It can't see your other conversations, your contacts, or other collaborators' repos. If the agent is compromised, the blast radius is limited to the threads you chose to share.

Multi-account

This works across multiple email accounts — Gmail, Protonmail, self-hosted — each with its own labels and routing rules, all funneling through the same scoped mailbox model.

Collaborator workflow

Each collaborator — human or agent — gets a scoped directory with:

mailboxes/{name}/
  AGENTS.md          # Full instructions: formats, commands, status flow
  CLAUDE.md          # Symlink for Claude Code auto-discovery
  README.md          # Quick-start guide
  voice.md           # Writing style guidelines
  contacts/          # Per-contact context for drafting
  conversations/     # Synced threads (read-only for the collaborator)
  drafts/            # Where the collaborator writes replies

The collaborator reads conversations, drafts replies following the documented format, validates with corky draft validate, and pushes. The owner reviews and sends.

Building

Developer setup

git clone https://github.com/btakita/corky.git
cd corky
cp .corky.toml.example mail/.corky.toml   # configure your email accounts
make release                               # build + symlink to .bin/corky

Make targets

make build        # Debug build
make release      # Release build + symlink to .bin/corky
make test         # Run tests
make clippy       # Lint
make check        # Lint + test
make install      # Install to ~/.cargo/bin
make precommit    # Full pre-commit checks

.gitignore

The following are gitignored:

.env
.corky.toml
credentials.json
*.credentials.json
CLAUDE.local.md
AGENTS.local.md
mail
.idea/
tmp/
target/
.bin/

Config files (.corky.toml, voice.md) live inside mail/ which is already gitignored. credentials.json is also gitignored in mail/.gitignore.

Conventions

Code style

  • Use serde derive for all data types
  • Use anyhow for application errors, thiserror for domain errors
  • Use toml_edit for format-preserving TOML edits (add-label)
  • Use std::process::Command for git operations (not git2)
  • Use regex + once_cell::Lazy for compiled regex patterns
  • Keep sync, draft, mailbox, contact logic in separate modules

Instruction files

  • AGENTS.md is canonical (committed). CLAUDE.md is a symlink.
  • Personal overrides: CLAUDE.local.md / AGENTS.local.md (gitignored).
  • Each module directory can contain its own AGENTS.md with package-specific conventions.
  • Keep the root AGENTS.md focused on cross-cutting concerns.
  • Actionable over informational. Instruction files contain the minimum needed to generate correct code. Reference material belongs in README.md.
  • Update with the code. When a change affects patterns, conventions, or module boundaries, update instruction files as part of the same change.
  • Combined root + package files should stay well under 1000 lines.

Version management

  • Never bump versions automatically — the user will bump versions explicitly.
  • Commits that include a version change should include the version number in the commit message.
  • Use BREAKING CHANGE: prefix in VERSIONS.md entries for incompatible changes.
  • Update SPEC.md when corky functionality changes (commands, formats, algorithms).

Workflow

Follow a research → plan → implement cycle:

  1. Research — Read the relevant code deeply. Document findings in research.md.
  2. Plan — Write a detailed implementation plan in plan.md.
  3. Todo — Produce a granular todo list from the approved plan.
  4. Implement — Execute the plan. Run make check continuously.
  5. Precommit — Run make precommit and corky audit-docs before committing.

Note: This is the full functional specification for Corky.

Corky Functional Specification

Language-independent specification for the corky email sync and collaboration tool. This document captures the exact behavior a port must reproduce.

1. Overview

Corky syncs email threads from IMAP providers into a flat directory of Markdown files, supports AI-assisted drafting, manages mailbox sharing via git submodules or plain directories, and pushes routing intelligence to Cloudflare.

2. Data Directory

2.1 Layout

{data_dir}/
  conversations/        # One .md file per thread
  drafts/               # Outgoing email drafts
  contacts/             # Per-contact context
    {name}/
      AGENTS.md
      CLAUDE.md -> AGENTS.md
  mailboxes/            # Named mailboxes (plain dirs or git submodules)
    {name}/
      conversations/
      drafts/
      contacts/
      AGENTS.md
      CLAUDE.md -> AGENTS.md
      README.md
      voice.md
      .gitignore
  manifest.toml         # Thread index (generated by sync)
  .sync-state.json      # IMAP sync state

2.2 Resolution Order

The data directory is resolved at runtime in this order:

  1. mail/ directory in current working directory (developer workflow)
  2. CORKY_DATA environment variable
  3. App config mailbox (see §2.4)
  4. ~/Documents/mail (hardcoded fallback)

2.3 Config Directory

Config always lives inside the data directory (mail/).

Config files: .corky.toml, voice.md, credentials.json

2.4 App Config

Location: {platformdirs.user_config_dir("corky")}/config.toml

  • Linux: ~/.config/corky/config.toml
  • macOS: ~/Library/Application Support/corky/config.toml
  • Windows: %APPDATA%/corky/config.toml

Stores named mailboxes (data directory references) and a default. Used in resolution step 3.

Mailbox resolution (when no explicit name given):

  1. default_mailbox set → use that mailbox
  2. Exactly one mailbox → use it implicitly
  3. Multiple mailboxes, no default → error with list
  4. No mailboxes → return None (fall through to step 4)

3. File Formats

3.1 Conversation Markdown

# {Subject}

**Labels**: {label1}, {label2}
**Accounts**: {account1}, {account2}
**Thread ID**: {thread_key}
**Last updated**: {RFC 2822 date}

---

## {Sender Name} <{email}> — {RFC 2822 date}

**To**: {recipient1}, {recipient2}
**CC**: {cc1}

{Body text}

---

## {Reply sender} — {date}

{Body text}

Per-message **To**: and **CC**: lines are emitted after the message header when non-empty. Old files without these lines parse correctly (fields default to empty).

Metadata regex: ^\*\*(.+?)\*\*:\s*(.+)$ (multiline) Message header regex: ^## (.+?) — (.+)$ (multiline, em dash U+2014)

3.2 Draft Markdown

# {Subject}

**To**: {recipient}
**CC**: {optional}
**Status**: draft
**Author**: {name}
**Account**: {optional — account name from .corky.toml}
**From**: {optional — email address, used to resolve account}
**In-Reply-To**: {optional — message ID}

---

{Draft body}

Required fields: # Subject heading, **To**, --- separator Recommended fields: **Status**, **Author** Status values: draftreviewapprovedsent Valid send statuses (for draft push --send): review, approved

3.3 .corky.toml

[owner]
github_user = "username"
name = "Display Name"

[accounts.{name}]
provider = "gmail"          # gmail | protonmail-bridge | imap
user = "you@gmail.com"
password = ""               # Inline password (not recommended)
password_cmd = ""           # Shell command to retrieve password
labels = ["correspondence"]
imap_host = ""              # Auto-filled by provider preset
imap_port = 993
imap_starttls = false
smtp_host = ""
smtp_port = 465
drafts_folder = "Drafts"
sync_days = 3650            # How far back to sync
default = false             # Mark one account as default

[contacts.{name}]
emails = ["addr@example.com"]

[routing]
for-alex = ["mailboxes/alex"]
shared = ["mailboxes/alice", "mailboxes/bob"]

[mailboxes.alex]
auto_send = false

[watch]
poll_interval = 300         # Seconds between polls
notify = false              # Desktop notifications

Password resolution order:

  1. password field (inline)
  2. password_cmd (run shell command, strip trailing whitespace)
  3. Error if neither set

Label scoping syntax: account:label (e.g. "proton-dev:INBOX") binds a label to a specific account.

3.4 .sync-state.json

{
  "accounts": {
    "{account_name}": {
      "labels": {
        "{label_name}": {
          "uidvalidity": 12345,
          "last_uid": 67890
        }
      }
    }
  }
}

3.5 manifest.toml

[threads.{slug}]
subject = "Thread Subject"
thread_id = "thread key"
labels = ["label1", "label2"]
accounts = ["account1"]
last_updated = "RFC 2822 date"
contacts = ["contact-name"]

Generated after each sync by scanning conversation files and matching sender emails against [contacts] in .corky.toml.

3.6 config.toml (App Config)

default_mailbox = "personal"

[mailboxes.personal]
path = "~/Documents/mail"

[mailboxes.work]
path = "~/work/mail"

Top-level fields:

  • default_mailbox: name of the default mailbox (set automatically to the first mailbox added)

Mailbox fields:

  • path: absolute or ~-relative path to a mail data directory

4. Algorithms

4.1 Thread Slug Generation

fn slugify(text: &str) -> String:
    text = text.to_lowercase()
    text = regex_replace(r"[^a-z0-9]+", "-", text)
    text = text.trim_matches('-')
    text = text[..min(60, text.len())]
    if text.is_empty(): return "untitled"
    return text

Slug collisions: If {slug}.md exists, try {slug}-2.md, {slug}-3.md, etc.

4.2 Thread Key Derivation

fn thread_key_from_subject(subject: &str) -> String:
    regex_replace(r"^(re|fwd?):\s*", "", subject.to_lowercase().trim())

Strips one layer of Re: or Fwd: prefix (case-insensitive), then lowercases.

4.3 Message Deduplication

Messages are deduplicated by (from, date) tuple. If both match an existing message in the thread, the message is skipped but labels/accounts metadata is still updated.

4.4 Multi-Source Accumulation

When the same thread is fetched from multiple labels or accounts:

  • Labels are appended (no duplicates)
  • Accounts are appended (no duplicates)
  • Messages are merged and deduplicated
  • Messages are sorted by parsed date

4.5 Label Routing

Labels in the [routing] section of .corky.toml route to configured mailbox directories. Fan-out: one label can route to multiple mailboxes (array of paths). Plain labels (no routing entry) route to {data_dir}/conversations/. Routing values are paths like mailboxes/{name}, resolved relative to data_dir, with /conversations/ appended.

Account:label syntax ("proton-dev:INBOX"):

  • Only matches when syncing the named account
  • The IMAP folder used is the part after the colon

4.6 Manifest Generation

After sync, scan all .md files in conversations/:

  1. Parse each file back into a Thread object
  2. For each message, extract emails from from, to, and cc fields (<email> regex)
  3. Match against [contacts] email→name mapping in .corky.toml
  4. Write manifest.toml with thread metadata and matched contacts

A contact appears in the manifest if they sent, received, or were CC'd on any message in the thread.

5. Commands

5.1 init

corky init --user EMAIL [PATH] [--provider PROVIDER]
             [--password-cmd CMD] [--labels LABEL,...] [--github-user USER]
             [--name NAME] [--mailbox-name NAME] [--sync] [--force]
  • PATH: project directory (default: . — current directory)
  • Creates {path}/mail/{conversations,drafts,contacts}/ with .gitkeep files
  • Generates .corky.toml at {path}/mail/
  • Installs voice.md at {path}/mail/ if not present
  • If inside a git repo: adds mail to .gitignore
  • Installs the email skill to .claude/skills/email/
  • Registers the project dir as a named mailbox in app config
  • --force: overwrite existing config; without it, exit 1 if .corky.toml exists
  • --sync: set CORKY_DATA env, run sync
  • --provider: gmail (default), protonmail-bridge, imap
  • --labels: default correspondence (comma-separated)
  • --mailbox-name: mailbox name to register (default: "default")

5.1.1 install-skill

corky install-skill NAME
  • Install an agent skill into the current directory
  • Currently supported: email (installs .claude/skills/email/SKILL.md and README.md)
  • Skips files that already exist (never overwrites)
  • Works from any directory (mailbox repos ship the skill automatically via mb add/mb reset)

5.2 sync

corky sync                   # incremental IMAP sync (default)
corky sync full              # full IMAP resync (ignore saved state)
corky sync account NAME      # sync one account
corky sync routes            # apply routing to existing conversations
corky sync mailbox [NAME]    # push/pull shared mailboxes

Bare corky sync runs incremental IMAP sync for all configured accounts. Subcommands:

  • full: ignore saved state, re-fetch all messages within sync_days
  • account NAME: sync only the named account
  • routes: apply [routing] rules to existing conversations/*.md files, copying matching threads into mailbox conversations/ directories
  • mailbox [NAME]: git push/pull shared mailbox repos (alias for mailbox sync)

Exit code: 0 on success.

5.3 sync-auth

corky sync-auth

Gmail OAuth setup. Requires credentials.json from Google Cloud Console. Runs a local server on port 3000 for the OAuth callback. Outputs the refresh token for .env.

5.4 list-folders

corky list-folders [ACCOUNT]

Without argument: lists available account names. With argument: connects to IMAP and lists all folders with flags.

5.5 draft push

corky draft push FILE [--send]
corky mailbox draft push FILE [--send]

Alias: corky push-draft (hidden, backwards-compatible).

Default: creates a draft via IMAP APPEND to the drafts folder. --send: sends via SMTP. Requires Status to be review or approved. After sending, updates Status field in the file to sent.

Account resolution for sending:

  1. **Account** field → match by name in .corky.toml
  2. **From** field → match by email address
  3. Fall back to default account

5.6 add-label

corky add-label LABEL --account NAME

Text-level TOML edit to add a label to an account's labels array. Preserves comments and formatting. Returns false if label already present.

5.7 contact-add (hidden alias)

corky contact-add NAME --email EMAIL [--email EMAIL2]

Hidden backward-compatible alias for contact add. The --label and --account flags are accepted but ignored.

5.8 watch

corky watch [--interval N]

IMAP polling daemon. Syncs all accounts, then pushes to shared mailboxes. Desktop notifications on new messages if notify = true in .corky.toml. Clean shutdown on SIGTERM/SIGINT.

5.9 audit-docs

corky audit-docs

Checks instruction files (AGENTS.md, README.md, SKILL.md) against codebase:

  • Referenced paths exist on disk
  • uv run scripts are registered
  • Type conventions (msgspec, not dataclasses)
  • Combined line budget (1000 lines max)
  • Staleness (docs older than source)

5.10 help

corky help [FILTER]
corky --help

Shows command reference. Optional filter matches command names.

5.11 mailbox add

corky mailbox add NAME --label LABEL [--name NAME] [--github] [--pat] [--public] [--account ACCT] [--org ORG]

Alias: corky mb add

Without --github: creates a plain directory at mailboxes/{name}/ with conversations/drafts/contacts subdirectories and template files (AGENTS.md, README.md, voice.md, .gitignore). With --github: creates a private GitHub repo ({org}/to-{name}), initializes with template files, adds as git submodule at mailboxes/{name}/. Updates .corky.toml.

--github: use a git submodule instead of a plain directory --pat: PAT-based access (prints instructions instead of GitHub collaborator invite) --public: public repo visibility --org: override GitHub org (default: owner's github_user)

5.12 mailbox sync

corky mailbox sync [NAME]

Alias: corky mb sync

For each mailbox (or one named): git pull --rebase, copy voice.md if newer, sync GitHub Actions workflow, stage+commit+push local changes, update submodule ref in parent. Skips git ops for plain (non-submodule) directories.

5.13 mailbox status

corky mailbox status

Alias: corky mb status

Shows incoming/outgoing commit counts for each mailbox submodule.

5.14 mailbox remove

corky mailbox remove NAME [--delete-repo]

Alias: corky mb remove

For plain directories: rm -rf mailboxes/{name}/. For submodules: git submodule deinit -f, git rm, clean up .git/modules/{path}. Removes from .corky.toml. --delete-repo: interactively confirms, then deletes GitHub repo via gh.

5.15 mailbox rename

corky mailbox rename OLD NEW [--rename-repo]

Alias: corky mb rename

Moves mailboxes/{old} to mailboxes/{new}. Uses git mv for submodules, mv for plain dirs. Updates .corky.toml. --rename-repo: also rename the GitHub repo via gh repo rename.

5.16 mailbox reset

corky mailbox reset [NAME] [--no-sync]

Alias: corky mb reset

Pull latest, regenerate all template files (AGENTS.md, README.md, CLAUDE.md symlink, .gitignore, voice.md, notify.yml) at mailboxes/{name}/, commit, push. --no-sync: regenerate files without pull/push.

5.17 unanswered

corky unanswered [SCOPE] [--from NAME]
corky mailbox unanswered [SCOPE] [--from NAME]

Alias: corky find-unanswered (hidden, backwards-compatible).

Scans conversations for threads where the last message sender doesn't match --from.

Scope argument:

  • Omitted → scan root conversations/ + all mailboxes/*/conversations/
  • . → root conversations/ only
  • NAMEmailboxes/{name}/conversations/ only

--from resolution: CLI flag > [owner] name in .corky.toml > error.

Output is grouped by scope when scanning multiple directories.

Sender regex: ^## (.+?) — (multiline, em dash)

5.18 draft validate

corky draft validate [FILE|SCOPE...]
corky mailbox draft validate [FILE|SCOPE...]

Alias: corky validate-draft (hidden, backwards-compatible).

Validates draft files. Checks: subject heading, required fields (To), recommended fields (Status, Author), valid status value, --- separator, non-empty body.

Scope argument (when no files given):

  • Omitted → scan root drafts/ + all mailboxes/*/drafts/
  • . → root drafts/ only
  • NAMEmailboxes/{name}/drafts/ only

Exit code: 0 if all valid, 1 if any errors.

5.19 mailbox list

corky mailbox list

Lists all registered mailboxes with paths. Marks the default mailbox. If no mailboxes configured, prints setup instructions.

5.20 Global --mailbox Flag

corky --mailbox NAME <subcommand> [args...]

Available on all commands. Resolves the named mailbox via app config and sets CORKY_DATA before dispatching to the subcommand.

5.21 draft new

corky draft new SUBJECT --to EMAIL [--cc EMAIL] [--account NAME] [--from EMAIL]
                [--in-reply-to MSG-ID] [--mailbox NAME]
corky mailbox draft new SUBJECT --to EMAIL [...]

Scaffolds a new draft file with pre-filled metadata.

Output: creates drafts/YYYY-MM-DD-{slug}.md and prints the path.

  • --mailbox NAME: create in mailboxes/{name}/drafts/ instead of root drafts/
  • --cc: CC recipient
  • --account: sending account name from .corky.toml
  • --from: sending email address
  • --in-reply-to: message ID for threading
  • Author resolved from [owner] name in .corky.toml
  • Slug collisions handled with -2, -3 suffix (same as sync)

5.22 contact add

corky contact add NAME --email EMAIL [--email EMAIL2]
corky contact add --from SLUG [NAME]

Creates {data_dir}/contacts/{name}/ with AGENTS.md template and CLAUDE.md symlink. Updates .corky.toml with the contact's email addresses.

Manual mode (--email): requires NAME positional. Creates contact with default AGENTS.md.

From-conversation mode (--from):

  1. Find conversations/{slug}.md or mailboxes/*/conversations/{slug}.md
  2. Parse thread, extract non-owner participants from from, to, cc fields
  3. Filter owner emails via accounts.*.user in .corky.toml
  4. Single participant: auto-derive name from display name (slugified)
  5. Multiple participants: require positional NAME to select one
  6. Generate enriched AGENTS.md with pre-filled Topics, Formality, Tone, and Research sections

--from and --email conflict (clap conflicts_with).

5.23 contact info

corky contact info NAME

Aggregates and displays contact information:

  1. Contact config from .corky.toml (emails)
  2. contacts/{name}/AGENTS.md content
  3. Matching threads from manifest.toml (root) and mailboxes/*/manifest.toml
  4. Summary: thread count, last activity date

Threads are matched where the contacts array in manifest contains NAME.

6. Sync Algorithm

6.1 State

Per-account, per-label state: (uidvalidity: u32, last_uid: u32)

6.2 Incremental Sync

For each account, for each label:

  1. SELECT the IMAP folder
  2. Check UIDVALIDITY — if changed from stored value, do full sync
  3. If incremental: SEARCH UID {last_uid+1}:*, filter out <= last_uid
  4. If full: SEARCH SINCE {today - sync_days}
  5. For each UID: FETCH RFC822, parse email, merge to thread file
  6. Update (uidvalidity, last_uid) in state

6.3 Message Parsing

From RFC822:

  • Subject: email.header.decode_header() (handles encoded words)
  • From: email.header.decode_header()
  • To: email.header.decode_header() (comma-separated recipients)
  • CC: email.header.decode_header() (comma-separated recipients)
  • Date: raw header string
  • Body: walk multipart for text/plain without Content-Disposition, or get payload for non-multipart
  • Thread key: thread_key_from_subject(subject)

6.4 Merge

For each message:

  1. Find existing thread file by scanning **Thread ID** metadata in all .md files
  2. If found, parse back into Thread object
  3. Check dedup: (from, date) tuple
  4. If new: append message, sort by date, update last_date
  5. Accumulate labels and accounts
  6. Write markdown, set file mtime to last message date

6.5 Orphan Cleanup

On --full sync: track all files written/updated. After sync, delete any .md files in conversations/ not in the touched set.

6.6 State Persistence

State is saved only after all accounts complete successfully. If sync crashes mid-way, state is not saved — next run re-fetches.

7. Mailbox Lifecycle

7.1 Add

Without --github (plain directory):

  1. Create mailboxes/{name}/ with conversations/drafts/contacts subdirectories
  2. Write template files (AGENTS.md, CLAUDE.md symlink, README.md, voice.md, .gitignore, .claude/skills/email/)
  3. Update .corky.toml

With --github (submodule):

  1. Create GitHub repo (gh repo create)
  2. Add collaborator (gh api repos/.../collaborators/...) or print PAT instructions
  3. Clone to temp dir, write template files, commit, push
  4. Add as git submodule at mailboxes/{name}/
  5. Update .corky.toml

7.2 Sync

  1. git pull --rebase in submodule (skipped for plain directories)
  2. Copy voice.md if root copy is newer
  3. Sync workflow template if newer
  4. Stage, commit, push local changes (skipped for plain directories)
  5. Update submodule ref in parent (git add {submodule_path}) (skipped for plain directories)

7.3 Status

For each mailbox submodule:

  1. git fetch
  2. git rev-list --count HEAD..@{u} (incoming)
  3. git rev-list --count @{u}..HEAD (outgoing)

7.4 Remove

For plain directories: rm -rf mailboxes/{name}/. For submodules:

  1. git submodule deinit -f
  2. git rm -f
  3. Clean up .git/modules/{path}

Then: 4. Remove from .corky.toml 5. Optionally delete GitHub repo (interactive confirmation)

7.5 Rename

  1. Move mailboxes/{old} to mailboxes/{new} (git mv for submodules, mv for plain dirs)
  2. Optionally gh repo rename
  3. Update .corky.toml entry

7.6 Reset

  1. git pull --rebase (submodules only)
  2. Regenerate: AGENTS.md, CLAUDE.md (symlink), README.md, .gitignore, voice.md, .claude/skills/email/ at mailboxes/{name}/
  3. Stage, commit, push (submodules only)
  4. Update submodule ref in parent (submodules only)

8. Draft Lifecycle

8.1 Create

Manual: create file in {data_dir}/drafts/ or {data_dir}/mailboxes/{name}/drafts/. Filename convention: YYYY-MM-DD-{slug}.md.

8.2 Validate

corky draft validate checks format. See section 5.18.

8.3 Push / Send

corky draft push FILE: IMAP APPEND to drafts folder. corky draft push FILE --send: SMTP send, update Status to sent.

Account resolution: Account field → From field → default account.

9. Watch Daemon

9.1 Poll Loop

while not shutdown:
    for each account:
        sync_account(full=false)
    save_state()
    count_new = compare uid snapshots before/after
    if count_new > 0:
        sync_mailboxes()
        notify(count_new)
    wait(interval) or shutdown

9.2 Signals

SIGTERM, SIGINT → clean shutdown (finish current poll, then exit).

9.3 Notifications

  • macOS: osascript -e 'display notification ...'
  • Linux: notify-send
  • Silently degrades if tool not installed.

9.4 Config

[watch] section in .corky.toml:

  • poll_interval: seconds (default 300)
  • notify: bool (default false)

CLI --interval overrides config.

10. Provider Presets

Fieldgmailprotonmail-bridgeimap (generic)
imap_hostimap.gmail.com127.0.0.1(required)
imap_port9931143993
imap_starttlsfalsetruefalse
smtp_hostsmtp.gmail.com127.0.0.1(required)
smtp_port4651025465
drafts_folder[Gmail]/DraftsDraftsDrafts

Preset values are defaults — any field explicitly set on the account wins.

11. Account Resolution

11.1 Password

  1. password field (inline string)
  2. password_cmd (shell command, capture stdout, strip trailing whitespace)
  3. Raise error if neither set

11.2 Sending Account

For draft push:

  1. **Account** metadata field → lookup by name in .corky.toml
  2. **From** metadata field → lookup by email address (case-insensitive)
  3. Default account (first with default = true, or first in file)
  4. Credential bubbling (see §11.3)

11.3 Credential Bubbling

When a draft lives inside a mailboxes/ subtree, the child mailbox may not have its own IMAP/SMTP credentials. Corky resolves credentials bottom-up:

  1. Check the leaf mailbox's .corky.toml for matching account credentials
  2. Walk parent directories upward, checking each .corky.toml for an account whose user matches the **From** address
  3. First match wins
  4. If no credentials found at any level, bail with error

This enables child mailboxes to draft replies that the parent's account sends.

Versions

Corky is alpha software. Expect breaking changes between minor versions.

Use BREAKING CHANGE: prefix in version entries to flag incompatible changes.

0.7.0

BREAKING CHANGE: Rust rewrite. Complete port from Python to Rust.

  • Rust rewrite: All ~3,750 LOC of Python replaced with ~6,700 LOC of Rust. Single crate, no workspace. All 25 CLI commands ported with identical behavior and file formats.
  • BREAKING CHANGE: Python removal: pyproject.toml, uv.lock, and all .py files removed. Install via cargo install corky instead of pip install corky.
  • Dependencies: clap (CLI), serde/toml/toml_edit (config), imap/native-tls (IMAP), mailparse (email parsing), lettre (SMTP), chrono (dates), directories (platform dirs), tokio (watch daemon), anyhow/thiserror (errors).
  • Format-preserving TOML: add-label now uses toml_edit crate (cleaner than Python's regex approach).
  • Gmail OAuth: Stub that prints instructions to use Python corky sync-auth for one-time setup. Full OAuth port deferred.
  • No behavioral changes: All file formats, sync algorithms, collaborator workflows, and CLI interfaces are identical to 0.6.1. Existing mail/ data repos are fully compatible.

Migration from 0.6.x: Uninstall Python corky (pip uninstall corky), install Rust binary (cargo install corky or download from releases). No data migration needed — all file formats are unchanged.

0.6.1

Lower Python requirement to 3.11+, app config with multi-space support.

  • Python 3.11+: Lowered minimum from 3.12. No 3.12-specific features were used; tomllib and datetime.UTC (3.11+) are the actual floor.
  • App config (src/app_config.py): New module for persistent configuration via platformdirs. Stores named spaces in ~/.config/corky/config.toml (Linux), ~/Library/Application Support/corky/config.toml (macOS), %APPDATA%/corky/config.toml (Windows).
  • Spaces: corky spaces lists configured spaces. corky init registers a space automatically. corky --space NAME <cmd> selects a specific space for any command.
  • Data dir resolution updated: New 4-step order: mail/ in cwd → CORKY_DATA env → app config space → ~/Documents/mail fallback.

0.6.0

Path resolution, corky init, and functional specification.

  • corky init: New command to initialize a data directory for general users. corky init --user you@gmail.com creates ~/Documents/mail with directory structure, accounts.toml, and empty config files. Supports --provider, --password-cmd, --labels, --github-user, --name, --sync, --force, --data-dir flags.
  • Path resolution (src/resolve.py): New module centralizes all path resolution. Data directory resolves in order: mail/ in cwd (developer), CORKY_DATA env, ~/Documents/mail (general user). Config directory: . if local mail/ exists, otherwise same as data dir.
  • BREAKING CHANGE: Removed module-level path constants: CONFIG_PATH removed from accounts.py, collab/__init__.py, contact/__init__.py. CONTACTS_DIR removed from contact/add.py. STATE_FILE and CONVERSATIONS_DIR removed from sync/imap.py. CREDENTIALS_FILE removed from sync/auth.py. VOICE_FILE removed from collab/add.py, collab/sync.py, collab/reset.py. _DIR_PREFIXES removed from collab/rename.py. All replaced by resolve.*() function calls. Tests that monkeypatched these constants must now patch resolve.<function> instead.
  • SPEC.md: Language-independent functional specification for an eventual Rust port. Covers file formats, algorithms (slugify, thread key, dedup, label routing), all 18 commands, sync algorithm, collaborator lifecycle, provider presets.

Migration from 0.5.x: If your code or tests monkeypatch CONFIG_PATH, CONTACTS_DIR, STATE_FILE, CONVERSATIONS_DIR, CREDENTIALS_FILE, VOICE_FILE, or _DIR_PREFIXES, switch to patching resolve.accounts_toml, resolve.contacts_dir, resolve.sync_state_file, resolve.conversations_dir, resolve.credentials_json, resolve.voice_md, or resolve.data_dir respectively.

0.5.0

Directional collaborator repos, nested CLI, owner identity, GitHub username keys.

  • Rename shared/ to for/{gh-user}/: Collaborator submodules now live under for/{github_user}/ instead of shared/{name}/. Repo naming: {owner}/to-{collab-gh}. This supports multi-user corky setups where each party has directional directories (for/ outgoing, by/ incoming).
  • BREAKING CHANGE: collab-*for * / by *: Flat collab-add, collab-sync, collab-remove, collab-rename, collab-reset, collab-status replaced with nested for add, for sync, for remove, for rename, for reset, for status. find-unanswered and validate-draft moved to by find-unanswered and by validate-draft. Standalone find-unanswered and validate-draft entry points kept for uvx use.
  • Owner identity: New [owner] section in accounts.toml with github_user and optional name. Required for collaborator features.
  • GitHub username as collaborator key: collaborators.toml section keys are now GitHub usernames (was display names). github_user is derived from the key. Optional name field stores display name.
  • Auto-derived repo: repo field in collaborators.toml is auto-derived as {owner_gh}/to-{collab_gh} if omitted.
  • for add CLI change (was collab-add): Positional arg is now GITHUB_USER (was NAME). --github-user flag removed (redundant). Added --name flag for display name.
  • Parameterized templates: AGENTS.md and README.md templates use owner name from config instead of hardcoded "Brian".
  • Help output now groups commands into sections: corky commands, collaborator commands (for/by), dev commands.
  • .gitignore: shared/ replaced with for/ and by/.

Migration from 0.4.x: Remove existing shared/ submodules (git submodule deinit -f shared/{name} && git rm -f shared/{name}), update collaborators.toml keys to GitHub usernames, add [owner] section to accounts.toml, re-add collaborators with corky for add. Replace collab-* with for * and find-unanswered / validate-draft with by find-unanswered / by validate-draft in scripts and aliases. Run for reset to update shared repo templates.

0.4.1

Add-label command and audit-docs fixes.

  • corky add-label LABEL --account NAME: Add a label to an account's sync config via text-level TOML edit (preserves comments).
  • contact-add integration: --label + --account automatically adds label to account sync config.
  • audit-docs: Fix tree parser for symlink-to-directory entries.
  • SKILL.md: Updated to reflect flat conversation directory, contacts, manifest.

0.4.0

Flat conversation directory, contacts, manifest.

  • Flat conversations: All threads in mail/conversations/ as [slug].md. No account or label subdirectories. Consolidates correspondence across multiple email accounts into one directory.
  • Immutable filenames: Slug derived from subject on first write, never changes. Thread identity tracked by **Thread ID** metadata.
  • File mtime: Set to last message date via os.utime(). ls -t sorts by thread activity.
  • Multi-source accumulation: Threads fetched from multiple labels or accounts accumulate all sources in **Labels** and **Accounts** metadata.
  • Orphan cleanup: --full sync deletes files not touched during the run.
  • manifest.toml: Generated after sync. Indexes threads by labels, accounts, contacts, and last-updated date.
  • Contacts: contacts.toml maps contacts to email addresses. Per-contact AGENTS.md in mail/contacts/{name}/ provides drafting context. corky contact-add scaffolds new contacts.
  • tomli-w: Added as dependency for TOML writing.
  • Backward-compatible parsing of legacy **Label** format.

0.3.0

IMAP polling daemon.

  • corky watch polls IMAP on an interval and syncs automatically.
  • Configurable poll interval and desktop notifications via accounts.toml [watch] section.
  • systemd and launchd service templates.

0.2.1

Maintenance release.

  • CI workflow: ty, ruff, pytest on push and PR.

0.2.0

Collaborator tooling and multi-account support.

  • collab-reset command (now for reset): pull, regenerate templates, commit and push.
  • Reframed docs as human-and-agent friendly.
  • account:label scoped routing for collaborators.
  • list-folders command and self-signed cert support.
  • Multi-account IMAP support via accounts.toml with provider presets.
  • Collaborator tooling: collab-add (now for add), collab-sync (now for sync), collab-remove (now for remove), find-unanswered (now by find-unanswered), validate-draft (now by validate-draft).
  • Multi-collaborator architecture with submodule-based sharing.

0.1.0

Renamed to corky. Unified CLI dispatcher.

  • corky CLI with subcommands.
  • push-draft command to create drafts or send emails from markdown.
  • Incremental IMAP sync with --full option.
  • Gmail sync workspace with drafting support.