Files

12 KiB

Archived source note: imap-email-triage

This reference preserves the former imap-email-triage SKILL.md content as source material absorbed into email-workflows.

The original skill package had support files and was archived intact; relative links in the original body below may refer to that archived package, not this umbrella reference. Support files were not flattened here to avoid broken package integrity.

Original support files:

  • references/apple-icloud-caldav-bill-calendar.md
  • references/direct-imap-triage-pattern.md


name: imap-email-triage description: "Inspect IMAP inbox emails for spam/phishing and bill-like messages; quarantine suspected spam and notify user about bills." version: 1.0.0 author: Hermes Agent license: MIT platforms: [linux, macos] metadata: hermes: tags: [email, imap, spam, phishing, bills, cron, notifications]

IMAP Email Triage

Use this skill when the user wants Hermes to periodically inspect an IMAP inbox, decide whether new messages are legitimate or suspected spam/phishing, move suspected spam to a quarantine folder such as Suspected Spam, and send notifications for likely bills.

Requirements

Prefer Himalaya CLI if configured, because it handles IMAP folders and message moves cleanly:

himalaya --version
himalaya account list
himalaya folder list

If Himalaya is not configured, collect IMAP details and configure ~/.config/himalaya/config.toml or implement a Python script with imaplib.

Needed from the user:

  1. IMAP host, port, encryption mode, username, and app password/OAuth credential command.
  2. Account/folder names: inbox folder, suspected spam folder, archive/trash policy.
  3. Whether to process unread only or all new messages since last run.
  4. Notification target: current Hermes chat, Telegram DM, Slack DM/channel, or calendar provider.
  5. Bill notification preferences: immediate DM, calendar reminder due date, lead time, ignored senders, vendors to recognize.
  6. Safety threshold for moving spam automatically; default: move only high-confidence suspected spam and leave uncertain messages in Inbox with a notification/log.

Default to every 10 minutes for normal inboxes. Use every 5 minutes only when the user wants near-real-time triage and the IMAP provider rate limits are acceptable.

Do not impose arbitrary message-count caps after the user says they control the mail server or otherwise authorizes full-mailbox processing. If the bottleneck is LLM context/output size rather than IMAP server load, solve that explicitly with chunking, deterministic pre-filtering, or a local classifier summary stage; do not frame a small cap as required for server safety.

See references/direct-imap-triage-pattern.md for a direct-IMAP implementation pattern, including protected password files, quoted folders with spaces, and unlimited collection semantics.

Safe Operating Rules

  1. Never permanently delete suspected spam. Move it to the user-approved spam/quarantine folder only. Prefer the exact existing webmail-visible spam folder the user selects (for example INBOX.spam), even if another IMAP folder has the \\Junk special-use flag.
  2. Create/verify the quarantine folder before enabling automation, or verify the existing spam folder and use that instead.
  3. Start with a dry run over recent messages and report classifications before moving anything.
  4. Maintain a processed-message cache keyed by Message-ID or IMAP UID to avoid repeated actions.
  5. Log every action with timestamp, message UID, sender, subject, decision, confidence, and reason.
  6. Fetch only headers and plain-text body/snippet unless attachment inspection is explicitly requested.
  7. Treat attachments and links as suspicious metadata; do not open links or execute attachments.
  8. Whitelist known legitimate senders, banks, utilities, payroll, and recurring vendors where appropriate.

Classification Rubric

Classify each message as one of:

  • legit: normal expected email.
  • suspected_spam: unsolicited marketing, obvious scam, phishing, spoofing, malware lure, fake invoice, or high-risk sender/auth mismatch.
  • bill_or_invoice: likely bill, invoice, statement, payment due, renewal, receipt needing attention, or overdue notice.
  • uncertain: insufficient confidence; do not move automatically.

Signals for suspected spam/phishing:

  • Sender domain mismatch or display-name spoofing.
  • User-maintained blocked sending domains are high-confidence suspected spam. When the user says to add sending domains to the spam list, update both the deterministic triage helper's blocked/base-domain list (for example USER_BLOCKED_SPAM_DOMAINS in /root/.hermes/scripts/imap_triage.py on this profile) and the active cron job prompt so future LLM runs treat those domains as move-to-spam. Match by base domain so subdomains such as offers.example.com inherit example.com.
  • After editing a blocked-domain list, verify with python3 -m py_compile /root/.hermes/scripts/imap_triage.py and a small import/probe of sender_reputation_hints() to confirm each requested domain returns a blocked-domain hint.
  • Low-reputation or abuse-prone sender domains/TLDs, especially unsolicited mail from .click, .xyz, .top, .site, .icu, .buzz, .quest, .monster, .sbs, .shop, .pw, .loan, .download, .stream, or similar domains.
  • Random-looking sender domains/subdomains, typo/lookalike domains, or domains with no clear relationship to the claimed sender.
  • Brand-spoof claims where the sender pretends to be a known brand from an unrelated/lookalike domain.
  • Sensitive financial/order/security claims from freemail senders, e.g. PayPal/Amazon/bank/payment notices from Gmail/Yahoo/Outlook accounts.
  • Suspicious links, URL shorteners, lookalike domains.
  • Unexpected attachments, macros, executables, password-protected archives.
  • Generic greeting plus financial/security demand.
  • Message claims to be from a known vendor but differs from historical sender/domain patterns.

Avoid false positives:

  • Do not classify newsletters as brand-spoof spam just because the subject mentions a brand, agency, or payment topic in a news headline. Example: a legitimate news newsletter headline mentioning Social Security is not a Social Security spoof unless the sender claims to be the Social Security Administration or asks for account/payment action.
  • Do not classify ordinary retail marketing from real brand domains as spam solely because the subject contains words like "lowest", "sale", "points", "reward", "bonus", or "season".

Signals for bill/invoice:

  • Subject/body contains invoice, bill, statement, payment due, amount due, renewal, past due, autopay, subscription, receipt, tax, premium, utilities.
  • Sender matches historical billing vendors.
  • Body includes due date, amount, account/customer number, invoice number, or payment link.
  • Message resembles previous legitimate bills from the same sender/domain.

Implementation Options

Prefer Himalaya CLI when it is installed and configured. If Himalaya is missing or the user wants a minimal dependency path, use Python stdlib imaplib directly instead. A direct-IMAP helper script works well for:

  • verifying TLS login with imaplib.IMAP4_SSL(host, 993, ssl_context=ssl.create_default_context())
  • listing folders via M.list()
  • collecting headers/body snippets via UID fetches
  • copying suspected spam to a quarantine folder with UID COPY, then marking the original \Deleted and expunging
  • keeping a local processed-UID state file and JSONL audit log under ~/.hermes/

Direct-IMAP scripts should use only protected password files or secret-manager commands, never hardcoded passwords. Use chmod 600 for password files and verify login before creating scheduled jobs.

IMAP folder names with spaces

When creating or selecting folders with spaces via Python imaplib, quote the mailbox name explicitly. imaplib.create('Suspected Spam') may be sent as two atoms and create the wrong folder (Suspected) on some servers. Use a quoted command or a helper that quotes/escapes mailbox names:

def q(name: str) -> str:
    return '"' + name.replace('\\\\', '\\\\\\\\').replace('"', '\\\\"') + '"'

M._simple_command('CREATE', q('Suspected Spam'))
M.uid('COPY', uid, q('Suspected Spam'))

After creation, list folders and clean up any accidental empty test folder only after verifying it contains zero messages.

Himalaya Workflow

List folders:

himalaya folder list

List recent inbox envelopes as JSON:

himalaya envelope list --folder INBOX --page-size 20 --output json

Read a candidate message:

himalaya message read <ID> --folder INBOX

Create the quarantine folder if missing (check installed Himalaya help for exact command; command names vary by version):

himalaya folder --help

Move high-confidence suspected spam:

himalaya message move <ID> "Suspected Spam" --folder INBOX

Message IDs can be folder-relative. Re-list after moves and prefer UID/Message-ID caches when scripting.

Cron Job Pattern

Use Hermes cron for the schedule. Attach this skill and the himalaya skill. The cron prompt should be self-contained and include:

  • Account name and folders.
  • Dry-run vs active mode.
  • Classification threshold.
  • Notification target and bill reminder policy.
  • Instruction to summarize actions and notify only on bill/uncertain/high-risk events.

Example schedule: every 10m.

Verification

Before enabling active mode:

  1. himalaya account list succeeds.
  2. himalaya folder list shows Inbox and Suspected Spam or the folder can be created.
  3. Dry run classifies a sample of recent messages without moving them.
  4. A test notification reaches the requested Slack/Telegram/current chat destination.
  5. Active run moves one known test spam message to Suspected Spam and logs the action.
  6. hermes cron status says Gateway is running — cron jobs will fire automatically.
  7. hermes cron list --all shows the triage job enabled with a future Next run.

Scheduler/Gateway Setup on Linux Servers

Hermes cron jobs fire from the gateway scheduler. If hermes cron status says the gateway is not running, the email triage script may work manually but scheduled automation will not run.

Recommended VPS setup:

hermes gateway install --system --run-as-user root --force
hermes gateway status
hermes cron status
hermes cron list --all
journalctl -u hermes-gateway -n 120 --no-pager

If the install command prompts for starting/enabling the service in a non-interactive run, answer yes to both prompts:

yes Y | hermes gateway install --system --run-as-user root --force

Use --run-as-user <service-user> instead of root on bare-metal hosts when a non-root Hermes profile owns the config. Running as root is acceptable for root-owned VPS/container installs where HERMES_HOME=/root/.hermes is the intended profile.

A gateway warning like No messaging platforms enabled does not by itself block cron scheduling; verify cron specifically with hermes cron status and the job's Next run advancement.

Pitfalls

  • IMAP folder names vary by provider (INBOX, Junk, [Gmail]/Spam, etc.). Always list folders first.
  • Gmail and Microsoft often require app passwords/OAuth, not the account password.
  • Some providers throttle frequent polling; increase interval if errors appear.
  • Calendar reminders require a configured calendar integration or a separate notification fallback.
  • For Apple Calendar/iCloud CalDAV bill events, use a protected app-specific-password file and the discovery/create flow in references/apple-icloud-caldav-bill-calendar.md.
  • A successful icloud.com web login with the user's normal Apple ID password does not validate CalDAV automation; iCloud CalDAV requires an Apple app-specific password and returns HTTP 401 when the server-side file contains the wrong or truncated credential.
  • LLM classification is probabilistic. Use conservative thresholds and keep an audit trail.