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.

install
source · Clone the upstream repo
git clone https://github.com/joelhooks/joelclaw
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/email-triage/SKILL.md
source content

Email 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
    gog gmail
    for triage
    bricks@aihero.dev
    , VIP threads, and all inboxes are in Front.
  • Use
    gog
    for Google Workspace operations (Calendar, Drive, Docs, etc.), not email triage.

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

open
,
archived
,
assigned
,
unassigned
,
snoozed
,
trashed
,
unreplied
,
waiting
,
resolved

There is NO

is:unread
. Use
is:unreplied
for conversations where the last message was inbound.

Status conflicts (cannot combine):

  • archived
    open
    trashed
    snoozed
    (mutually exclusive)
  • assigned
    unassigned
    (mutually exclusive)

Date filters — UNIX TIMESTAMPS ONLY

  • before:<unix_seconds>
    — messages/comments created before this timestamp
  • after:<unix_seconds>
    — messages/comments created after this timestamp
  • during:<unix_seconds>
    — messages/comments on same day as timestamp

Front does NOT accept

before:YYYY-MM-DD
— always convert to unix seconds. The CLI
--before
and
--after
flags accept
YYYY-MM-DD
and convert automatically.

Recipient filters

  • from:<handle>
    — sender (email, social handle)
  • to:<handle>
    — recipient (includes to/cc/bcc)
  • cc:<handle>
    ,
    bcc:<handle>
    — specific fields
  • recipient:<handle>
    — any role (from, to, cc, bcc)
  • Multiple same-type filters use OR logic:
    from:a@x.com from:b@x.com
    → either
  • Different filter types use AND logic:
    from:a@x.com to:b@x.com
    → both

Entity filters (use IDs, not names)

  • inbox:<inbox_id>
    — e.g.
    inbox:inb_41w25
  • tag:<tag_id>
    — e.g.
    tag:tag_13o8r1
  • assignee:<teammate_id>
    — e.g.
    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 (
    Re:
    ), questions, invitations to specific meetings, requests.
  • 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
  • joelclaw email inboxes
    lists them all with IDs
  • 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
    next_page_token
    when more results exist; pass via
    --page-token
  • [aih]
    subject prefix indicates AI Hero project threads via
    bricks@aihero.dev
    Google Group. Key participants: Alex Hillman (
    alex@indyhall.org
    ), Matt Pocock (
    mattpocockvoice@gmail.com
    ), Amy Hoy (
    team@stackingthebricks.com
    ). Treat as VIP: always surface, never archive.

Signals for "Reply Needed"

These are heuristics, not rules. Use judgment for each:

  • Re:
    prefix + real person sender (not a bot/noreply)
  • [aih]
    prefix — AI Hero collaboration via
    bricks@aihero.dev
    (Alex Hillman, Matt Pocock, Amy Hoy). VIP: always surface, never archive.
  • 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@
    ,
    notifications@
    senders with no actionable content
  • 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 (
    RESOLVED
    in subject)
  • 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.