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:
- You label threads in your email client. Only threads you explicitly label get synced locally.
- Labels route to scoped views. Each mailbox gives a collaborator or agent a directory containing only the threads labeled for them — nothing else.
- 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. - 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.
- Per-contact context. Each mailbox ships with
AGENTS.md(orCLAUDE.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
corkycommand. Scriptable and composable. - Single-binary install. One
curl | shgives collaboratorscorky unansweredandcorky draft validate. - Self-documenting repos. Each shared repo ships with
AGENTS.md,voice.md, and aREADME.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:
| Field | gmail | protonmail-bridge | imap (generic) |
|---|---|---|---|
| imap_host | imap.gmail.com | 127.0.0.1 | (required) |
| imap_port | 993 | 1143 | 993 |
| imap_starttls | false | true | false |
| smtp_host | smtp.gmail.com | 127.0.0.1 | (required) |
| smtp_port | 465 | 1025 | 465 |
| drafts_folder | [Gmail]/Drafts | Drafts | Drafts |
Any preset value can be overridden per-account.
Password resolution
passwordfield (inline string)password_cmd(shell command, captures stdout, strips trailing whitespace)- 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:
mail/directory in current working directoryCORKY_DATAenvironment variable- App config mailbox (named mailboxes)
~/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: defaultcorrespondence(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.
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 intent | Action |
|---|---|
| Follow-up, correction, or reply to a recent email | Threaded 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 person | New thread — no in_reply_to, fresh subject |
| Ambiguous | Default to reply if there's a recent conversation with the same contact; ask if unclear |
Gmail threading requirements:
In-Reply-ToandReferencesheaders 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:
**Account**field → match by name in.corky.toml**From**field → match by email address- Fall back to default account
- Credential bubbling: if the draft is inside a mailbox, walk parent directories for a
.corky.tomlwith 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: draft → review → approved → sent
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 unansweredandcorky draft validatein 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:
- You label threads in your email client. Only threads you explicitly label get synced locally.
- Labels route to scoped views. Each mailbox gives the collaborator/agent a directory containing only the threads labeled for them — nothing else.
- 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
serdederive for all data types - Use
anyhowfor application errors,thiserrorfor domain errors - Use
toml_editfor format-preserving TOML edits (add-label) - Use
std::process::Commandfor git operations (notgit2) - Use
regex+once_cell::Lazyfor compiled regex patterns - Keep sync, draft, mailbox, contact logic in separate modules
Instruction files
AGENTS.mdis canonical (committed).CLAUDE.mdis a symlink.- Personal overrides:
CLAUDE.local.md/AGENTS.local.md(gitignored). - Each module directory can contain its own
AGENTS.mdwith package-specific conventions. - Keep the root
AGENTS.mdfocused 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.mdwhen corky functionality changes (commands, formats, algorithms).
Workflow
Follow a research → plan → implement cycle:
- Research — Read the relevant code deeply. Document findings in
research.md. - Plan — Write a detailed implementation plan in
plan.md. - Todo — Produce a granular todo list from the approved plan.
- Implement — Execute the plan. Run
make checkcontinuously. - Precommit — Run
make precommitandcorky audit-docsbefore 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:
mail/directory in current working directory (developer workflow)CORKY_DATAenvironment variable- App config mailbox (see §2.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):
default_mailboxset → use that mailbox- Exactly one mailbox → use it implicitly
- Multiple mailboxes, no default → error with list
- 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: draft → review → approved → sent
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:
passwordfield (inline)password_cmd(run shell command, strip trailing whitespace)- 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/:
- Parse each file back into a Thread object
- For each message, extract emails from
from,to, andccfields (<email>regex) - Match against
[contacts]email→name mapping in.corky.toml - Write
manifest.tomlwith 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.gitkeepfiles - Generates
.corky.tomlat{path}/mail/ - Installs
voice.mdat{path}/mail/if not present - If inside a git repo: adds
mailto.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.tomlexists--sync: setCORKY_DATAenv, run sync--provider:gmail(default),protonmail-bridge,imap--labels: defaultcorrespondence(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.mdandREADME.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 withinsync_daysaccount NAME: sync only the named accountroutes: apply[routing]rules to existingconversations/*.mdfiles, copying matching threads into mailboxconversations/directoriesmailbox [NAME]: git push/pull shared mailbox repos (alias formailbox 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:
**Account**field → match by name in.corky.toml**From**field → match by email address- 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 runscripts 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/+ allmailboxes/*/conversations/ .→ rootconversations/onlyNAME→mailboxes/{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/+ allmailboxes/*/drafts/ .→ rootdrafts/onlyNAME→mailboxes/{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 inmailboxes/{name}/drafts/instead of rootdrafts/--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] namein.corky.toml - Slug collisions handled with
-2,-3suffix (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):
- Find
conversations/{slug}.mdormailboxes/*/conversations/{slug}.md - Parse thread, extract non-owner participants from
from,to,ccfields - Filter owner emails via
accounts.*.userin.corky.toml - Single participant: auto-derive name from display name (slugified)
- Multiple participants: require positional
NAMEto select one - 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:
- Contact config from
.corky.toml(emails) contacts/{name}/AGENTS.mdcontent- Matching threads from
manifest.toml(root) andmailboxes/*/manifest.toml - 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:
SELECTthe IMAP folder- Check
UIDVALIDITY— if changed from stored value, do full sync - If incremental:
SEARCH UID {last_uid+1}:*, filter out<= last_uid - If full:
SEARCH SINCE {today - sync_days} - For each UID:
FETCH RFC822, parse email, merge to thread file - 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/plainwithoutContent-Disposition, or get payload for non-multipart - Thread key:
thread_key_from_subject(subject)
6.4 Merge
For each message:
- Find existing thread file by scanning
**Thread ID**metadata in all.mdfiles - If found, parse back into Thread object
- Check dedup:
(from, date)tuple - If new: append message, sort by date, update
last_date - Accumulate labels and accounts
- 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):
- Create
mailboxes/{name}/with conversations/drafts/contacts subdirectories - Write template files (AGENTS.md, CLAUDE.md symlink, README.md, voice.md, .gitignore,
.claude/skills/email/) - Update
.corky.toml
With --github (submodule):
- Create GitHub repo (
gh repo create) - Add collaborator (
gh api repos/.../collaborators/...) or print PAT instructions - Clone to temp dir, write template files, commit, push
- Add as git submodule at
mailboxes/{name}/ - Update
.corky.toml
7.2 Sync
git pull --rebasein submodule (skipped for plain directories)- Copy
voice.mdif root copy is newer - Sync workflow template if newer
- Stage, commit, push local changes (skipped for plain directories)
- Update submodule ref in parent (
git add {submodule_path}) (skipped for plain directories)
7.3 Status
For each mailbox submodule:
git fetchgit rev-list --count HEAD..@{u}(incoming)git rev-list --count @{u}..HEAD(outgoing)
7.4 Remove
For plain directories: rm -rf mailboxes/{name}/.
For submodules:
git submodule deinit -fgit rm -f- Clean up
.git/modules/{path}
Then:
4. Remove from .corky.toml
5. Optionally delete GitHub repo (interactive confirmation)
7.5 Rename
- Move
mailboxes/{old}tomailboxes/{new}(git mvfor submodules,mvfor plain dirs) - Optionally
gh repo rename - Update
.corky.tomlentry
7.6 Reset
git pull --rebase(submodules only)- Regenerate: AGENTS.md, CLAUDE.md (symlink), README.md, .gitignore, voice.md,
.claude/skills/email/atmailboxes/{name}/ - Stage, commit, push (submodules only)
- 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
| Field | gmail | protonmail-bridge | imap (generic) |
|---|---|---|---|
| imap_host | imap.gmail.com | 127.0.0.1 | (required) |
| imap_port | 993 | 1143 | 993 |
| imap_starttls | false | true | false |
| smtp_host | smtp.gmail.com | 127.0.0.1 | (required) |
| smtp_port | 465 | 1025 | 465 |
| drafts_folder | [Gmail]/Drafts | Drafts | Drafts |
Preset values are defaults — any field explicitly set on the account wins.
11. Account Resolution
11.1 Password
passwordfield (inline string)password_cmd(shell command, capture stdout, strip trailing whitespace)- Raise error if neither set
11.2 Sending Account
For draft push:
**Account**metadata field → lookup by name in.corky.toml**From**metadata field → lookup by email address (case-insensitive)- Default account (first with
default = true, or first in file) - 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:
- Check the leaf mailbox's
.corky.tomlfor matching account credentials - Walk parent directories upward, checking each
.corky.tomlfor an account whoseusermatches the**From**address - First match wins
- 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.pyfiles removed. Install viacargo install corkyinstead ofpip 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-labelnow usestoml_editcrate (cleaner than Python's regex approach). - Gmail OAuth: Stub that prints instructions to use Python
corky sync-authfor 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;
tomllibanddatetime.UTC(3.11+) are the actual floor. - App config (
src/app_config.py): New module for persistent configuration viaplatformdirs. Stores named spaces in~/.config/corky/config.toml(Linux),~/Library/Application Support/corky/config.toml(macOS),%APPDATA%/corky/config.toml(Windows). - Spaces:
corky spaceslists configured spaces.corky initregisters 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_DATAenv → app config space →~/Documents/mailfallback.
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.comcreates~/Documents/mailwith directory structure,accounts.toml, and empty config files. Supports--provider,--password-cmd,--labels,--github-user,--name,--sync,--force,--data-dirflags.- Path resolution (
src/resolve.py): New module centralizes all path resolution. Data directory resolves in order:mail/in cwd (developer),CORKY_DATAenv,~/Documents/mail(general user). Config directory:.if localmail/exists, otherwise same as data dir. - BREAKING CHANGE: Removed module-level path constants:
CONFIG_PATHremoved fromaccounts.py,collab/__init__.py,contact/__init__.py.CONTACTS_DIRremoved fromcontact/add.py.STATE_FILEandCONVERSATIONS_DIRremoved fromsync/imap.py.CREDENTIALS_FILEremoved fromsync/auth.py.VOICE_FILEremoved fromcollab/add.py,collab/sync.py,collab/reset.py._DIR_PREFIXESremoved fromcollab/rename.py. All replaced byresolve.*()function calls. Tests that monkeypatched these constants must now patchresolve.<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/tofor/{gh-user}/: Collaborator submodules now live underfor/{github_user}/instead ofshared/{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 *: Flatcollab-add,collab-sync,collab-remove,collab-rename,collab-reset,collab-statusreplaced with nestedfor add,for sync,for remove,for rename,for reset,for status.find-unansweredandvalidate-draftmoved toby find-unansweredandby validate-draft. Standalonefind-unansweredandvalidate-draftentry points kept foruvxuse. - Owner identity: New
[owner]section inaccounts.tomlwithgithub_userand optionalname. Required for collaborator features. - GitHub username as collaborator key:
collaborators.tomlsection keys are now GitHub usernames (was display names).github_useris derived from the key. Optionalnamefield stores display name. - Auto-derived repo:
repofield incollaborators.tomlis auto-derived as{owner_gh}/to-{collab_gh}if omitted. for addCLI change (wascollab-add): Positional arg is nowGITHUB_USER(wasNAME).--github-userflag removed (redundant). Added--nameflag 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 withfor/andby/.
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-addintegration:--label+--accountautomatically 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 -tsorts by thread activity. - Multi-source accumulation: Threads fetched from multiple labels or accounts accumulate all sources in
**Labels**and**Accounts**metadata. - Orphan cleanup:
--fullsync deletes files not touched during the run. - manifest.toml: Generated after sync. Indexes threads by labels, accounts, contacts, and last-updated date.
- Contacts:
contacts.tomlmaps contacts to email addresses. Per-contactAGENTS.mdinmail/contacts/{name}/provides drafting context.corky contact-addscaffolds new contacts. - tomli-w: Added as dependency for TOML writing.
- Backward-compatible parsing of legacy
**Label**format.
0.3.0
IMAP polling daemon.
corky watchpolls 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-resetcommand (nowfor reset): pull, regenerate templates, commit and push.- Reframed docs as human-and-agent friendly.
account:labelscoped routing for collaborators.list-folderscommand and self-signed cert support.- Multi-account IMAP support via
accounts.tomlwith provider presets. - Collaborator tooling:
collab-add(nowfor add),collab-sync(nowfor sync),collab-remove(nowfor remove),find-unanswered(nowby find-unanswered),validate-draft(nowby validate-draft). - Multi-collaborator architecture with submodule-based sharing.
0.1.0
Renamed to corky. Unified CLI dispatcher.
corkyCLI with subcommands.push-draftcommand to create drafts or send emails from markdown.- Incremental IMAP sync with
--fulloption. - Gmail sync workspace with drafting support.