#!/usr/bin/env bash set -euo pipefail ROOT="/home/node/.openclaw/workspace" STATE_DIR="$ROOT/mail/state" RUN_DIR="$ROOT/mail/runs" STATE_FILE="$STATE_DIR/tasks.json" ACTION_FILE="$STATE_DIR/action-items.json" MAX_ITEMS="${EMAIL_REVIEW_MAX_ITEMS:-10}" WEBHOOK_URL="${N8N_EMAIL_WEBHOOK_URL:-}" NOTIFY="${EMAIL_REVIEW_NOTIFY:-1}" mkdir -p "$STATE_DIR" "$RUN_DIR" if [[ -z "$WEBHOOK_URL" ]]; then echo "ERROR: N8N_EMAIL_WEBHOOK_URL is required" >&2 exit 2 fi if [[ ! -f "$STATE_FILE" ]]; then cat > "$STATE_FILE" <<'JSON' { "schemaVersion": 1, "updatedAt": null, "items": [] } JSON fi if [[ ! -f "$ACTION_FILE" ]]; then cat > "$ACTION_FILE" <<'JSON' { "schemaVersion": 1, "updatedAt": null, "items": [] } JSON fi RUN_TS="$(date -u +%Y%m%dT%H%M%SZ)" RUN_FILE="$RUN_DIR/run-$RUN_TS.json" TMP_RESP="$(mktemp)" TMP_ITEMS="$(mktemp)" trap 'rm -f "$TMP_RESP" "$TMP_ITEMS"' EXIT curl -sS -X POST "$WEBHOOK_URL" \ -H 'content-type: application/json' \ -d '{}' > "$TMP_RESP" jq '{fetchedAt: now|todate, response: .}' "$TMP_RESP" > "$RUN_FILE" if ! jq -e '.emails? // [] | type == "array"' "$TMP_RESP" >/dev/null 2>&1; then echo "ERROR: n8n response missing emails[] array" >&2 exit 3 fi jq -c '.emails // [] | .[]' "$TMP_RESP" | head -n "$MAX_ITEMS" > "$TMP_ITEMS" || true NEW_COUNT=0 UPDATED_COUNT=0 TOTAL=0 while IFS= read -r email_json; do [[ -z "$email_json" ]] && continue TOTAL=$((TOTAL+1)) MESSAGE_ID="$(jq -r '.messageId // empty' <<<"$email_json")" SUBJECT="$(jq -r '.subject // ""' <<<"$email_json")" FROM_ADDR="$(jq -r '.from.value[0].address // .from.text // ""' <<<"$email_json")" RECEIVED_AT="$(jq -r '.date // empty' <<<"$email_json")" BODY="$(jq -r '(.textPlain // .snippet // .textHtml // "") | tostring' <<<"$email_json" | sed 's/\s\+/ /g' | cut -c1-8000)" [[ -z "$MESSAGE_ID" ]] && continue TRIAGE_INPUT="$(jq -cn \ --arg messageId "$MESSAGE_ID" \ --arg subject "$SUBJECT" \ --arg from "$FROM_ADDR" \ --arg receivedAt "$RECEIVED_AT" \ --arg body "$BODY" \ '{type:"email_review", messageId:$messageId, subject:$subject, from:$from, receivedAt:$receivedAt, body:$body}')" TRIAGE_RAW="$(openclaw agent --agent mail-triage --message "$TRIAGE_INPUT" --json 2>/dev/null || true)" TRIAGE_TEXT="$(jq -r '.result.payloads[0].text // empty' <<<"$TRIAGE_RAW" 2>/dev/null || true)" if [[ -z "$TRIAGE_TEXT" ]]; then continue fi TRIAGE_JSON="$(jq -c . <<<"$TRIAGE_TEXT" 2>/dev/null || true)" if [[ -z "$TRIAGE_JSON" ]]; then continue fi ITEM_KEY="$MESSAGE_ID" EXISTING="$(jq -c --arg k "$ITEM_KEY" '.items[]? | select(.messageId==$k)' "$STATE_FILE")" RECORD="$(jq -cn \ --arg messageId "$MESSAGE_ID" \ --arg updatedAt "$(date -u +%FT%TZ)" \ --argjson source "$email_json" \ --argjson triage "$TRIAGE_JSON" \ '{messageId:$messageId, updatedAt:$updatedAt, source:$source, triage:$triage}')" if [[ -z "$EXISTING" ]]; then jq --argjson rec "$RECORD" --arg ts "$(date -u +%FT%TZ)" '.items += [$rec] | .updatedAt=$ts' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE" NEW_COUNT=$((NEW_COUNT+1)) SUMMARY="$(jq -r '.summary // "(no summary)"' <<<"$TRIAGE_JSON")" echo "NEW ACTIONABLE EMAIL: $SUBJECT" echo "- from: $FROM_ADDR" echo "- summary: $SUMMARY" else OLD_HASH="$(jq -Sc '.triage' <<<"$EXISTING" | sha256sum | awk '{print $1}')" NEW_HASH="$(jq -Sc . <<<"$TRIAGE_JSON" | sha256sum | awk '{print $1}')" if [[ "$OLD_HASH" != "$NEW_HASH" ]]; then jq --arg k "$ITEM_KEY" --argjson rec "$RECORD" --arg ts "$(date -u +%FT%TZ)" ' .items = (.items | map(if .messageId==$k then $rec else . end)) | .updatedAt=$ts ' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE" UPDATED_COUNT=$((UPDATED_COUNT+1)) echo "UPDATED EMAIL TRIAGE: $SUBJECT" fi fi done < "$TMP_ITEMS" # Build normalized action list for downstream reminder/overview use. jq --arg ts "$(date -u +%FT%TZ)" ' . as $root | { schemaVersion: 1, updatedAt: $ts, items: [ $root.items[]? | { messageId, emailSubject: (.source.subject // ""), from: (.source.from.value[0].address // .source.from.text // ""), receivedAt: (.source.date // null), priority: (.triage.priority // "unknown"), urgency: (.triage.urgency // "unknown"), no_action_needed: (.triage.no_action_needed // false), summary: (.triage.summary // ""), actions: ( [(.triage.actions // [])[]? | { action: (.action // ""), owner: (.owner // "Ben"), deadline: (.deadline // null), estimated: (.estimated // false), evidence: (.evidence // "") }] ) } ] } ' "$STATE_FILE" > "$ACTION_FILE" SUMMARY_LINE="email-review-run complete: total=$TOTAL new=$NEW_COUNT updated=$UPDATED_COUNT" echo "$SUMMARY_LINE" if [[ "$NOTIFY" == "1" && $((NEW_COUNT + UPDATED_COUNT)) -gt 0 ]]; then openclaw system event --text "Email review: $NEW_COUNT new, $UPDATED_COUNT updated actionable item(s)." --mode now >/dev/null 2>&1 || true fi