166 lines
5.1 KiB
Bash
Executable File
166 lines
5.1 KiB
Bash
Executable File
#!/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
|