Joelclaw email-triage
Triage Joel's email inboxes via the joelclaw email CLI. Scan, categorize, archive noise, surface actionable items, and draft replies. Use when: 'check my email', 'scan inbox', 'triage email', 'what needs a reply', 'clean up inbox', 'archive junk', 'email summary', 'anything important in email', or any request involving email inbox review or cleanup.
git clone https://github.com/joelhooks/joelclaw
T=$(mktemp -d) && git clone --depth=1 https://github.com/joelhooks/joelclaw "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/email-triage" ~/.claude/skills/joelhooks-joelclaw-email-triage && rm -rf "$T"
skills/email-triage/SKILL.mdEmail Triage
Scan Joel's email inboxes (Front), triage conversations by importance, archive noise, and surface items needing attention. All operations use the
joelclaw email CLI.
Canonical Email Interface
- Front is canonical for all email triage in joelclaw.
- Never use
for triage —gog gmail
, VIP threads, and all inboxes are in Front.bricks@aihero.dev - Use
for Google Workspace operations (Calendar, Drive, Docs, etc.), not email triage.gog
Front Search API Reference
The search endpoint is
GET /conversations/search/{url_encoded_query}.
Results sorted by last activity date (not configurable).
Valid is:
statuses
is:open, archived, assigned, unassigned, snoozed, trashed, unreplied, waiting, resolved
There is NO
. Use is:unread
is:unreplied for conversations where the last message was inbound.
Status conflicts (cannot combine):
↔archived
↔open
↔trashed
(mutually exclusive)snoozed
↔assigned
(mutually exclusive)unassigned
Date filters — UNIX TIMESTAMPS ONLY
— messages/comments created before this timestampbefore:<unix_seconds>
— messages/comments created after this timestampafter:<unix_seconds>
— messages/comments on same day as timestampduring:<unix_seconds>
Front does NOT accept
— always convert to unix seconds.
The CLI before:YYYY-MM-DD
--before and --after flags accept YYYY-MM-DD and convert automatically.
Recipient filters
— sender (email, social handle)from:<handle>
— recipient (includes to/cc/bcc)to:<handle>
,cc:<handle>
— specific fieldsbcc:<handle>
— any role (from, to, cc, bcc)recipient:<handle>- Multiple same-type filters use OR logic:
→ eitherfrom:a@x.com from:b@x.com - Different filter types use AND logic:
→ bothfrom:a@x.com to:b@x.com
Entity filters (use IDs, not names)
— e.g.inbox:<inbox_id>inbox:inb_41w25
— e.g.tag:<tag_id>tag:tag_13o8r1
— e.g.assignee:<teammate_id>assignee:tea_hjx3
,participant:<teammate_id>
,author:<teammate_id>
,mention:<teammate_id>commenter:<teammate_id>
,contact:<contact_id>link:<link_id>custom_field:"<name>=<value>"
Free text
Bare words search subject + body. Phrases in quotes:
"exact phrase".
No negation
Front search has no negation — no
-from:, no NOT, no exclusion operators.
Max 15 filters per query.
CLI Commands
Scan inbox
joelclaw email inbox # default: is:open, 50 results joelclaw email inbox -n 100 # more results joelclaw email inbox -q "is:open is:unreplied" # awaiting reply joelclaw email inbox --from notifications@github.com # by sender joelclaw email inbox --before 2026-02-01 # auto-converts to unix ts joelclaw email inbox --after 2026-01-15 # auto-converts to unix ts joelclaw email inbox --page-token "TOKEN" # pagination (from previous response)
Flags can be combined:
joelclaw email inbox --from matt@totaltypescript.com --after 2026-02-01 -n 20
Archive single
joelclaw email archive --id cnv_xxx
Archive multiple by ID (fast, parallel, batched)
joelclaw email archive-ids --ids cnv_abc,cnv_def,cnv_ghi
Runs 10 concurrent requests. Best for triaging a page of results — grab the IDs of noise and blast them.
Archive bulk by query (dry-run by default)
joelclaw email archive-bulk -q "is:open from:notifications@github.com" # dry run — shows count + sample joelclaw email archive-bulk -q "is:open from:notifications@github.com" --confirm # execute joelclaw email archive-bulk -q "is:open from:notifications@github.com" --limit 100 --confirm # batch size
Read a conversation
joelclaw email read --id cnv_xxx joelclaw email read --id cnv_xxx --refresh # bypass local cache
joelclaw email read uses a local thread + attachment cache (default TTL: 60 minutes). Cache path: ~/.cache/joelclaw/email/. Use --refresh to bypass cache and force a fresh fetch from Front.
Read output shape + jq patterns
joelclaw email read returns:
result.conversation: {id, subject, status, from: {name, email}, date, tags} result.messages[]: {id, from: {name, email}, date, is_inbound, body_preview (or body), attachments[]} result.attachment_summary: {total, cached, metadata_only, cache_errors} result.cache: {hit, source, cache_path, age_seconds, ttl_seconds}
Message content is currently in
body_preview, with a migration to body underway. Treat both as valid for backwards compatibility.
Correct extraction pattern (current field):
joelclaw email read --id cnv_xxx 2>&1 | jq '.result.messages[] | {from: .from.email, date: .date, body: .body_preview}'
Backwards/forwards-compatible extraction pattern:
joelclaw email read --id cnv_xxx 2>&1 | jq '.result.messages[] | {from: .from.email, date: .date, body: (.body // .body_preview)}'
List inboxes
joelclaw email inboxes
Triage Workflow
1. Load inbox state
joelclaw email inbox -n 50
Parse the JSON result. Each conversation has:
id, subject, from (name + email), date, status, tags.
Useful follow-up queries:
joelclaw email inbox -q "is:open is:unreplied" -n 50 # awaiting reply joelclaw email inbox --before 2026-01-01 -n 50 # old stuff to clean joelclaw email inbox --from notifications@github.com # CI noise check
2. Categorize using inference
Read each conversation and decide its category based on sender, subject, and context. Do NOT use hardcoded domain lists. Use judgment:
- Reply needed — Real people expecting a response. Colleagues, collaborators, friends, business contacts with personal messages. Look for reply threads (
), questions, invitations to specific meetings, requests.Re: - Read later — Interesting content worth saving. Newsletters Joel subscribes to intentionally (The Information, Astral Codex Ten, Lenny's Newsletter), industry news, technical deep-dives.
- Actionable — Requires action but not a reply. Bills, security alerts, expiring trials, tax documents, delivery updates for real orders, calendar invites needing RSVP.
- Archive — No value. Marketing spam, promotional offers, cold outreach, duplicate notifications, resolved alerts, automated reports nobody reads, vendor upsells.
3. Present triage summary
Organize findings for Joel. Lead with what matters:
## 🔴 Reply needed (N) - **Name** — Subject (why it needs a reply) ## ⚡ Actionable (N) - **Sender** — Subject (what action) ## 📖 Read later (N) - **Source** — Subject ## 🗑️ Archive candidates (N) - Count by type (e.g., "14 marketing, 8 CI failures, 5 duplicate notifications")
4. Execute decisions
Fastest path for noise: scan a page, collect noise IDs, use
archive-ids:
joelclaw email archive-ids --ids cnv_aaa,cnv_bbb,cnv_ccc,cnv_ddd
For sender-based cleanup: use
archive-bulk with from: filter:
joelclaw email archive-bulk -q "is:open from:notifications@github.com" --limit 100 --confirm
Rate limiting: Front allows ~50 req/s. If archiving large batches (>200), add pauses between batches or expect 429s. The CLI handles batching for
archive-ids (10 concurrent), but archive-bulk iterates page-by-page and may hit limits on very large sets.
Read before deciding:
joelclaw email read --id cnv_xxx
Key Context
- Joel has 8 Front inboxes across joel@egghead.io, joelhooks@gmail.com, joel@skillrecordings.com, joel@badass.dev, joel.hooks@vercel.com, LinkedIn, DMs, and Inngest
lists them all with IDsjoelclaw email inboxes- Joel's teammate ID:
tea_hjx3 - The CLI handles Front API auth automatically via
secrets lease front_api_token - Draft-then-approve for replies — never send directly
- Front search results are eventually consistent — recently archived items may still appear in search for a few minutes
- Pagination: responses include
when more results exist; pass vianext_page_token--page-token
subject prefix indicates AI Hero project threads via[aih]
Google Group. Key participants: Alex Hillman (bricks@aihero.dev
), Matt Pocock (alex@indyhall.org
), Amy Hoy (mattpocockvoice@gmail.com
). Treat as VIP: always surface, never archive.team@stackingthebricks.com
Signals for "Reply Needed"
These are heuristics, not rules. Use judgment for each:
prefix + real person sender (not a bot/noreply)Re:
prefix — AI Hero collaboration via[aih]
(Alex Hillman, Matt Pocock, Amy Hoy). VIP: always surface, never archive.bricks@aihero.dev- Direct questions in subject line
- Meeting invitations from known contacts
- Threads where Joel was the last sender and someone replied
- Slack notification summaries (may indicate missed conversations worth checking)
Signals for "Archive"
,noreply@
,no-reply@
senders with no actionable contentnotifications@- Marketing subject patterns: "LAST chance", "% off", "deal", "unlock", "limited time"
- Duplicate conversations (same subject from same sender — archive all but most recent)
- Resolved monitoring alerts (
in subject)RESOLVED - CI failure notifications older than 24h (stale — either fixed or won't be)
- Cold outreach from unknown senders with sales language
Common Noise Senders (bulk-archive candidates)
These are historically high-volume, low-signal senders. Use
archive-bulk with from: filter:
notifications@github.com — CI failures, dependabot, PR notifications alerts@alerts.betterstack.com — resolved uptime alerts no-reply@is.email.nextdoor.com — neighborhood spam
Always dry-run first to verify the query matches what you expect.