Claude Mail

audited · cautionFree

Peer review in your statusline: see plans, diffs, and pages other Claude Code users sent you, who they're from, and pending requests at a glance. Approve or reject without leaving the terminal. Part of Claude Mail (github.com/bflynn4141/claude-mail).

by Flynnjamm1 installsmailreviewcollaborationinboxstatusline
Claude Code
>
5 from @sethgold, @mia-builds, @kaidev +1 ·2 req | Context: 72% remaining
? for shortcuts⏵⏵ accept edits on
Security auditby claude-opus-4-8 · verdict: caution

This statusline displays a 'mail' badge for the Claude Mail peer-review service (pending messages, senders, recent verdicts, request counts) plus context-remaining and git-branch segments parsed from stdin JSON. It reads ~/.claude-mail/state.json and credentials.json. When the cache is older than 60s it spawns a detached background curl that reads a bearer token from credentials.json and sends it in an Authorization header to a fixed Cloudflare Workers relay (claude-mail.bflynn4141.workers.dev, overridable via $CLAUDE_MAIL_RELAY) to download fresh state JSON, which is validated with jq and moved into place. It writes only inside ~/.claude-mail (state.json, temp file, a .refreshing lock). It runs jq, git, curl, date, stat as subprocesses.

networkwrites-filesreads-homesubprocessesenv-vars

Audits are advisory. This script runs on your machine — read it before installing.

The script — read it before you run it

#!/bin/bash
# claude-mail statusline — mail badge + the user's original context/git segments.
# Reads ONLY the local cache (~/.claude-mail/state.json); never blocks on network.
# If the cache is >60s old, kicks a detached background refresh and shows stale data.

input=$(cat)

DIR="$HOME/.claude-mail"
STATE="$DIR/state.json"
CREDS="$DIR/credentials.json"
RELAY="${CLAUDE_MAIL_RELAY:-https://claude-mail.bflynn4141.workers.dev}"

# ── background refresh when stale ──────────────────────────────────────────
if [ -f "$CREDS" ]; then
  now=$(date +%s)
  mtime=$(stat -f %m "$STATE" 2>/dev/null || echo 0)
  if [ $((now - mtime)) -gt 60 ] && ! [ -f "$DIR/.refreshing" ]; then
    (
      touch "$DIR/.refreshing"
      bearer=$(jq -r '.bearer' "$CREDS" 2>/dev/null)
      curl -s -m 8 -H "Authorization: Bearer $bearer" "$RELAY/state" -o "$STATE.tmp" \
        && jq -e '.handle' "$STATE.tmp" >/dev/null 2>&1 \
        && mv "$STATE.tmp" "$STATE"
      rm -f "$DIR/.refreshing" "$STATE.tmp"
    ) >/dev/null 2>&1 &
    disown
  fi
fi

# ── mail badge ──────────────────────────────────────────────────────────────
BLUE=$'\033[34m'; GREEN=$'\033[32m'; DIM=$'\033[2m'; RESET=$'\033[0m'
mail=""
if [ -f "$STATE" ]; then
  pending=$(jq -r '.pending | length' "$STATE" 2>/dev/null || echo 0)
  if [ "$pending" -eq 1 ]; then
    from=$(jq -r '.pending[0].from_handle' "$STATE")
    title=$(jq -r '.pending[0].title' "$STATE" | cut -c1-28)
    mail="${BLUE}✉ 1${RESET} · \"${title}\" from @${from}"
  elif [ "$pending" -gt 1 ]; then
    # distinct senders, newest first; cap at 3 then "+n"
    senders=$(jq -r '[.pending[].from_handle] | reduce .[] as $h ([]; if index($h) then . else . + [$h] end)
      | if length > 3 then (.[0:3] | map("@" + .) | join(", ")) + " +\(length - 3)"
        else (map("@" + .) | join(", ")) end' "$STATE")
    mail="${BLUE}✉ ${pending}${RESET} from ${senders}"
  else
    verdict=$(jq -r '.recentVerdicts[0] // empty | "\(.status) \"\(.title)\" by @\(.to_handle)"' "$STATE" 2>/dev/null)
    if [ -n "$verdict" ]; then
      mail="${GREEN}✓${RESET} ${verdict}"
    fi
  fi
  requests=$(jq -r '.requests.total // 0' "$STATE" 2>/dev/null || echo 0)
  if [ "$requests" -gt 0 ]; then
    [ -n "$mail" ] && mail="$mail ${DIM}·${RESET} "
    mail="${mail}${DIM}${requests} req${RESET}"
  fi
elif [ ! -f "$CREDS" ]; then
  mail="${DIM}✉ mail_link to start${RESET}"
fi

# ── original segments: context remaining + git branch ───────────────────────
cwd=$(echo "$input" | jq -r '.workspace.current_dir')
branch=$(cd "$cwd" 2>/dev/null && git -c core.useBuiltinFSMonitor=false rev-parse --abbrev-ref HEAD 2>/dev/null)
remaining=$(echo "$input" | jq -r '.context_window.remaining_percentage // empty')

out=""
[ -n "$mail" ] && out="$mail"
[ -n "$remaining" ] && { [ -n "$out" ] && out="$out | "; out="${out}Context: ${remaining}% remaining"; }
[ -n "$branch" ] && { [ -n "$out" ] && out="$out | "; out="${out}git: $branch"; }
echo "$out"

Reviews

no reviews yet