Skip to main content

Hazel Downloads Inbox Automation System

Hazel Downloads Inbox Automation System

Overview

This lab documents a local automation system built using Hazel to treat the Downloads folder as an inbox.

The system enforces deterministic file routing, time-based lifecycle handling, metadata-driven classification, and duplicate cleanup. The goal is to turn Downloads from a junk drawer into a controlled intake point for school, church, certificates, and finance files.

Environment

ComponentDetail
OSmacOS
AutomationHazel
Metadata layerFinder tags
Root target~/Documents
Inbox~/Downloads

Folder Targets

Run once to provision the full directory tree:

mkdir -p ~/Documents/{Certificates,Church-Program,Career,Finance/{Receipts,Invoices},School/{Assignments,IT/{BAP115,IT125\ Intro\ to\ information\ Tech,IT255\ Network\ Fundamentals},LS303,Religion/REL275B}}

Verify the tree after provisioning:

find ~/Documents -type d | sort

Tagging Strategy

Finder tags serve as the metadata layer between Hazel rules. Tags allow rules to pass state to downstream rules without moving or renaming files prematurely.

TagApplied ByPurposeConsumed By
excelclassManual (user)Marks BAP115 Excel courseworkRule 6 — BAP115 router
companyRule 5Marks vendor/company-origin filesManual review or future rule
processedAny terminal ruleMarks file as routed — prevents re-evaluationRule 14 — duplicate catch-all guard

Tagging Rules

  • Tags are additive only — no rule removes a tag
  • The processed tag is applied at the end of any rule that moves a file
  • Manual tags (excelclass) must be applied before the 7-day window closes
  • Tag names are lowercase, no spaces, no special characters

Applying Tags Manually in Finder

  1. Select file in Finder
  2. Right-click → Tags
  3. Type tag name and press Return

Or via terminal using the tag CLI:

## Install via Homebrew
brew install tag

## Apply a tag
tag --add excelclass ~/Downloads/excel-homework-week3.xlsx

## Verify tags on a file
tag --list ~/Downloads/excel-homework-week3.xlsx

Rule Order for Downloads

Rules execute top-to-bottom. Hazel stops at the first match. Order is the single most critical configuration decision.

PriorityRuleConditionAction
1PicturesExisting ruleKeep — do not modify
2ResumeExisting ruleKeep — do not modify
33D PrintingExisting ruleKeep — do not modify
4Certificate or CPEName or kind matches certificate/CPE patternMove → Certificates
5Company tagName contains company identifiersAdd tag company
6BAP115Has tag excelclass, last opened > 7 days agoMove → School/IT/BAP115
7IT255 Packet TracerName matches Packet Tracer pattern, last opened > 7 days agoMove → School/IT/IT255 Network Fundamentals
8IT125Name matches IT125 pattern, last opened > 7 days agoMove → School/IT/IT125 Intro to information Tech
9LS303Name matches LS303 pattern, last opened > 7 days agoMove → School/LS303
10REL275BName matches REL275B pattern, last opened > 7 days agoMove → School/Religion/REL275B
11Generic school projectsName matches school pattern, last opened > 7 days agoMove → School/Assignments
12Church programsName matches church pattern, last opened > 7 days agoRename + Move → Church-Program
13ReceiptsName or kind matches receipt patternRename + Move → Finance/Receipts
14Duplicate cleanupHazel duplicate detection + has tag processedTrash

Steps

Before / After Workflow

Before — Unmanaged Downloads

~/Downloads/
├── excel-homework-week3.xlsx ← sitting for 3 weeks, never moved
├── IT255_Lab4_PacketTracer.pkt ← buried, never routed
├── Church-Bulletin-2026-04-13.pdf ← mixed with everything else
├── Amazon_Receipt_March.pdf ← lost in the noise
├── Resume_ChrisNewcomb_2026.pdf ← duplicated 3 times
├── Resume_ChrisNewcomb_2026 (1).pdf
├── Resume_ChrisNewcomb_2026 (2).pdf
└── random_download.zip ← unknown, untouched

After — Inbox Model Running

~/Downloads/
└── random_download.zip ← unmatched, stays for manual triage

~/Documents/
├── Certificates/
├── Church-Program/
│ └── 2026-04-13_Church-Bulletin.pdf
├── Finance/
│ └── Receipts/
│ └── 2026-03_Amazon-Receipt.pdf
└── School/
└── IT/
├── BAP115/
│ └── excel-homework-week3.xlsx
└── IT255 Network Fundamentals/
└── IT255_Lab4_PacketTracer.pkt

Files that match no rule remain in Downloads for manual triage. This is intentional — the system does not move unknown files.

Shell Script — Sunday Validation

Run weekly to surface files that should have been routed but were not. This is an observability tool, not a replacement for Hazel rules.

#!/usr/bin/env bash
## validate-downloads.sh
## Surfaces Downloads files older than 7 days that were not routed by Hazel.
## Usage: bash validate-downloads.sh
## Schedule: Weekly, Sunday 9:00 AM via launchd

set -euo pipefail

DOWNLOADS="$HOME/Downloads"
THRESHOLD_DAYS=7
REPORT_FILE="$HOME/Documents/hazel-validation-$(date +%Y-%m-%d).txt"

echo "=== Hazel Downloads Validation ===" | tee "$REPORT_FILE"
echo "Run date: $(date)" | tee -a "$REPORT_FILE"
echo "Threshold: files not opened in ${THRESHOLD_DAYS}+ days" | tee -a "$REPORT_FILE"
echo "" | tee -a "$REPORT_FILE"

UNROUTED=$(find "$DOWNLOADS" -maxdepth 1 -type f -atime +"$THRESHOLD_DAYS" 2>/dev/null)

if [[ -z "$UNROUTED" ]]; then
echo "✓ No unrouted files found. Downloads inbox is clean." | tee -a "$REPORT_FILE"
else
echo "⚠ Unrouted files older than ${THRESHOLD_DAYS} days:" | tee -a "$REPORT_FILE"
echo "" | tee -a "$REPORT_FILE"
while IFS= read -r file; do
LAST_ACCESS=$(stat -f "%Sa" -t "%Y-%m-%d" "$file")
echo " [$LAST_ACCESS] $(basename "$file")" | tee -a "$REPORT_FILE"
done <<< "$UNROUTED"
fi

echo "" | tee -a "$REPORT_FILE"
echo "Report saved to: $REPORT_FILE"

Scheduling with launchd

Save as ~/Library/LaunchAgents/com.newcomb-labs.validate-downloads.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.newcomb-labs.validate-downloads</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/usr/local/bin/validate-downloads.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>0</integer>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/validate-downloads.log</string>
<key>StandardErrorPath</key>
<string>/tmp/validate-downloads.err</string>
</dict>
</plist>

Load the agent:

launchctl load ~/Library/LaunchAgents/com.newcomb-labs.validate-downloads.plist

Verify it loaded:

launchctl list | grep newcomb-labs

Duplicate Handling Strategy

Rule 14 is a catch-all that runs last. It only fires on files that satisfy two conditions simultaneously: Hazel's built-in duplicate detection AND the presence of the processed tag.

Decision Logic

File in Downloads


Is it a duplicate? (Hazel detection)

Yes │ No
│ └──→ Skip — not Rule 14's concern

Does it have tag `processed`?

Yes │ No
│ └──→ Skip — upstream rules have not fired yet; do not trash

Move to Trash

Why the Two-Condition Guard Matters

Without the processed guard, Rule 14 could trash a file that simply hasn't been opened yet and happens to share a name with another file. The two-condition check ensures only confirmed-routed duplicates are cleaned up. A file can be a duplicate and still be the version you want to keep.

Failure Modes and Edge Cases

ScenarioBehaviorResolution
File opened within 7-day windowTime-based rule does not fireExpected — file stays in Downloads until threshold passes
excelclass tag not applied before 7-day windowBAP115 rule never firesApply tag manually; rule fires on next Hazel evaluation cycle
File matches multiple rulesFirst matching rule winsReview rule order; more specific rules must sit above broader ones
Duplicate detected but not tagged processedRule 14 skips itCorrect behavior — upstream rules have not fired yet
Folder target does not existHazel move fails silentlyRe-run mkdir -p provisioning script; Hazel retries on next cycle
File renamed after landing in DownloadsName-based rules may not matchTag-based rules are more resilient; prefer tags for ambiguous filenames
Hazel not running (app quit)No rules executeAdd Hazel to Login Items: System Settings → General → Login Items
Large .pkt file slow to openLast-opened timestamp may not updateOpen file manually once to reset timestamp before 7-day window
Church bulletin named inconsistentlyChurch rule may not matchStandardize naming or broaden Hazel name condition to partial match

Implementation Artifacts

Hazel does not export rules natively. Screenshots of each configured rule are the primary artifact format.

Store at: website/static/img/labs/hazel-downloads/

Naming convention: rule-{priority:02d}-{slug}.png

website/static/img/labs/hazel-downloads/
├── rule-04-certificate.png
├── rule-05-company-tag.png
├── rule-06-bap115.png
├── rule-07-it255.png
├── rule-08-it125.png
├── rule-09-ls303.png
├── rule-10-rel275b.png
├── rule-11-school-generic.png
├── rule-12-church.png
├── rule-13-receipts.png
└── rule-14-duplicate-cleanup.png

Validation

  • mkdir -p provisioning script runs without errors
  • find ~/Documents -type d shows expected full folder tree
  • Each rule fires correctly on a test file matching its condition
  • Rule 14 does not trash files missing the processed tag
  • validate-downloads.sh runs and produces a dated report file
  • launchd agent loads without error (launchctl list | grep newcomb-labs)
  • No files older than 7 days remain unrouted after a full Hazel evaluation cycle

Lessons Learned

  • The inbox model forces intentional organization over passive accumulation
  • Time-based automation prevents premature moves on files still in active use
  • Rule order is the single most critical configuration decision in Hazel
  • Finder tags enable cross-rule metadata passing without filesystem side effects
  • Name-based matching is fragile — tags are more resilient for ambiguous filenames
  • The validation script surfaces Hazel gaps that rules alone cannot catch

Future Improvements

  • Add a Finance/Invoices rule to separate invoices from receipts
  • Evaluate tag CLI integration directly in Hazel shell action scripts
  • Export Hazel rules via AppleScript for version-controlled backup
  • Add a Career rule to route resume variants and job application documents
  • Integrate validation report with a macOS notification via osascript
  • Consider a quarantine tag for files that need manual review before routing
  • Explore Hazel's folder action chaining for multi-stage processing pipelines