Triage inbox emails, classify by urgency, and batch action them (delete, move, draft reply, skip).
Accounts
Two accounts are configured in himalaya. Use -a <name> to target a specific account:
| Name | Default | |
|---|---|---|
| harlanzw | harlan@harlanzw.com | yes |
| hotmail | harlan103@hotmail.com | no |
When no --account is specified, triage both accounts sequentially (harlanzw first, then hotmail). The hotmail account uses OAuth2 via Thunderbird's public client ID; if the token expires, re-auth with ortie auth get -a hotmail.
Gotchas
- IMAP connection timeouts -- himalaya connects over IMAP. If commands hang, the connection may have dropped. Retry once before reporting failure.
- Hotmail OAuth2 token expiry -- the hotmail access token expires after ~1 hour. If you get a keyring/auth error, run
ortie auth get -a hotmailto re-authenticate, then re-store tokens with the himalaya keyring attributes (service=himalaya-cli, username=hotmail-oauth2-access-token/refresh-token, target=default, application=rust-keyring). - Envelope IDs are session-volatile -- IDs change after moves/deletes. Always action emails in a single pass; do not cache IDs across steps.
- Delete = move to Trash --
himalaya message deletemoves to Trash unless already in Trash. Safe for triage. - template send is irreversible -- never send a reply without explicit user confirmation. Always save drafts first using
himalaya template save -f Drafts. - Large inboxes -- default page size is 50. If inbox has hundreds of unread, triage in batches using
--pageand--page-size. - Folder names with spaces/slashes -- quote folder names in commands:
himalaya message move "Finance/Stripe" 1234.
Data Storage
Track triage sessions:
echo "$(date -I) FOLDER TOTAL DELETED MOVED REPLIED SKIPPED" >> "${CLAUDE_PLUGIN_DATA}/email-triage-history.log"
On subsequent runs, show time since last triage.
Available Folders
These are the known folders for routing:
| Category | Folders |
|---|---|
| Core | INBOX, Drafts, Sent, Spam, Trash |
| Clients | Clients/inbound, Clients/odysseytraveller.com, Clients/harlanzw.com, Clients/massivemonster.co, Clients/Paspaley, Clients/Forgd, Clients/U7BUY, Clients/SuperDesign, Clients/old.harlanzw.com |
| Notifications | Notifications/GitHub, Notifications/NPM, Notifications/Slack |
| Finance | Finance/Airwallex, Finance/Kraken, Finance/Stripe, Finance/DigitalOcean, Finance/Porkbun, Finance/Personal, Finance/Anthropic, Finance/Misc Business, Finance/PayPal, Finance/Mercury |
| Projects | Projects/Nuxt SEO Pro, Projects/Request Indexing |
| Travel | Travel/NZ 2025, Travel/Chile 2025 |
| Other | Archives, House, Auto Purge, ZMNotification, ZMNewsLetter |
Workflow
Step 0: Parse arguments and check last triage
- If
$ARGUMENTSprovided, parse:--account <name>= triage a specific account (default: both)--folder <name>= triage a specific folder (default: INBOX)--unread= only unread messages (filter:not flag seen)--limit <n>= override page size (default: 50)--from <pattern>= filter by sender--follow-ups= switch to follow-up detection mode (see Step 8)
- Check
${CLAUDE_PLUGIN_DATA}/email-triage-history.logfor last triage timestamp - If
--follow-upsflag is set, skip to Step 8
Step 1: Fetch envelopes
himalaya envelope list -o json -s <limit> -f <folder> [QUERY]
Query examples:
- Unread only:
not flag seen - Recent:
after 2026-03-20 - From specific sender:
from github
Step 2: Parallel batch classification
Split envelopes into batches of 10, one parallel haiku classification agent per batch; classification is cheap mechanical extraction, so haiku is the right tier. For 10 or fewer envelopes, use a single agent.
Each agent receives a batch of envelopes (id, subject, from, date, flags) and classifies using references/heuristics.md, returning a JSON array conforming exactly to this schema (reject and re-run any batch with malformed entries):
interface EmailClassification {
id: string
urgency: 1 | 2 | 3 | 4 | 5
category: 'client' | 'finance' | 'notification' | 'newsletter' | 'spam' | 'personal' | 'project' | 'automated'
suggestedAction: 'reply' | 'move' | 'delete' | 'skip'
suggestedFolder: string
reason: string
}
Step 3: Read high-urgency emails
For any email classified as urgency 4-5 or suggestedAction: reply, read the full message body to provide better context:
himalaya message read <id> -f <folder> --no-headers
Read only the bodies you need to decide the action. Spawn the reads in parallel.
Step 4: Present triage table
Display merged results sorted by urgency (descending):
| # | From | Subject | Age | Urgency | Action | Destination | Reason |
|---|---|---|---|---|---|---|---|
| 5129 | CodeRabbit | Trial nearing... | 1d | 2 | delete | Trash | Automated trial notice |
| 5128 | GitHub Support | Report Abuse... | 2d | 3 | move | Notifications/GitHub | Support ticket update |
Group by suggested action:
- Delete (spam, expired promos, automated noise)
- Move (sorted by destination folder)
- Reply needed (show message preview)
- Skip (keep in inbox, needs manual attention)
Step 5: User confirmation
Present action summary and ask for confirmation:
📬 Triage Summary:
Delete: 12 emails
Move: 8 emails (3→Notifications/GitHub, 2→Finance/Stripe, 3→Clients/inbound)
Reply: 2 emails
Skip: 3 emails
Proceed? You can also:
- Remove specific IDs from actions: "skip 5129, 5130"
- Change action for an ID: "move 5129 to Finance/Stripe"
- "delete all newsletters"
- "show me 5128" to read a specific email
Allow iterating until user says "go", "proceed", or "do it".
Step 6: Execute actions
Execute in this order (to handle ID volatility):
Draft replies first -- for each reply email:
himalaya template reply <id> -f <folder>Show the generated template. Let user edit the body. Save to Drafts:
himalaya template save -f Drafts "<edited template>"Only send if user explicitly says "send" for that reply.
Move messages -- batch by destination:
himalaya message move "<target_folder>" <id1> <id2> <id3> -f <folder>Delete messages -- batch delete:
himalaya message delete <id1> <id2> <id3> -f <folder>
Step 7: Log and summarize
echo "$(date -I) <folder> <total> <deleted> <moved> <replied> <skipped>" >> "${CLAUDE_PLUGIN_DATA}/email-triage-history.log"
Show final summary with counts. If replies were drafted, remind user to review Drafts folder.
Step 8: Follow-up detection (when --follow-ups or trigger words used)
Detect sent emails that never got a reply. Useful for chasing up clients, support tickets, etc.
Fetch recent sent emails:
himalaya envelope list -o json -s <limit> -f Sent after <date>Default: last 14 days. Override with
--since <yyyy-mm-dd>.For each sent email, check for replies in INBOX: Search for responses by matching subject (strip "Re: "/"Fwd: " prefix) and recipient:
himalaya envelope list -o json -f INBOX subject "<stripped subject>" from "<original recipient>"Also check Trash and relevant subfolders in case the reply was already triaged.
Spawn parallel
haikuagents in batches of 10 to do this matching; use a single agent for 10 or fewer sent emails.Filter to unanswered only -- emails where no matching reply envelope was found.
Classify staleness:
Days since sent Status Suggested action 1-3 Fresh skip, too early to chase 4-7 Due gentle follow-up 8-14 Overdue direct follow-up 15+ Stale flag for manual decision, may no longer be relevant Present follow-up table:
To Subject Sent Days ago Status Action client@example.com Invoice #42 2026-03-18 7 Due draft follow-up support@github.com Abuse report 2026-03-15 10 Overdue draft follow-up User picks which to chase -- for each selected email:
himalaya template reply <original_sent_id> -f SentPre-fill with a polite follow-up nudge referencing the original subject. Save to Drafts. Only send on explicit confirmation.