default.nix
1 { 2 lib, 3 stdenv, 4 writeShellApplication, 5 isync, 6 notmuch, 7 afew, 8 coreutils, 9 gnugrep, 10 jq, 11 rbw, 12 w3m, 13 khard, 14 claude-code, 15 libnotify, 16 terminal-notifier, 17 }: 18 19 writeShellApplication { 20 name = "email-sync"; 21 22 runtimeInputs = [ 23 isync 24 notmuch 25 afew 26 coreutils 27 gnugrep 28 jq 29 rbw 30 w3m 31 khard 32 claude-code 33 ] 34 ++ lib.optionals stdenv.isLinux [ libnotify ] 35 ++ lib.optionals stdenv.isDarwin [ terminal-notifier ]; 36 37 text = '' 38 MAILDIR="''${MAILDIR:-$HOME/mail}" 39 NOTMUCH_CONFIG="''${NOTMUCH_CONFIG:-$HOME/.config/notmuch/default/config}" 40 ISYNC_CONFIG="''${ISYNC_CONFIG:-$HOME/.config/isyncrc}" 41 42 export NOTMUCH_CONFIG 43 44 # Prevent concurrent runs with lock directory (atomic on all platforms) 45 LOCKDIR="$HOME/.local/state/email-sync.lock" 46 mkdir -p "$(dirname "$LOCKDIR")" 47 cleanup() { rmdir "$LOCKDIR" 2>/dev/null || true; } 48 trap cleanup EXIT 49 if ! mkdir "$LOCKDIR" 2>/dev/null; then 50 echo "Another email-sync is running, exiting." 51 exit 0 52 fi 53 54 # Try to unlock rbw if locked (keychain will provide password automatically) 55 if ! rbw unlocked 2>/dev/null; then 56 echo "rbw vault is locked, attempting unlock via keychain..." 57 if ! rbw unlock 2>/dev/null; then 58 echo "Failed to unlock rbw vault, skipping sync." 59 exit 0 60 fi 61 fi 62 63 echo "Syncing emails from IMAP servers..." 64 mbsync -c "$ISYNC_CONFIG" -a 65 66 # Initialize notmuch if needed 67 if [ ! -d "$MAILDIR/.notmuch" ]; then 68 echo "Initializing notmuch database..." 69 notmuch new 70 fi 71 72 echo "Indexing new emails..." 73 notmuch new 74 75 echo "Tagging emails with afew (including Claude spam filter)..." 76 PYTHONPATH="$HOME/.config/afew:$PYTHONPATH" PYTHONWARNINGS="ignore::UserWarning" \ 77 python3 -c "import sys; sys.argv = ['afew', '-tn']; import afew_filters; from afew.commands import main; main()" || true 78 79 # Apply retention policies (cleanup old notifications) 80 echo "Applying retention policies..." 81 XDG_CONFIG_HOME="$HOME/.config/afew-cleanup" \ 82 PYTHONPATH="$HOME/.config/afew:$PYTHONPATH" PYTHONWARNINGS="ignore::UserWarning" \ 83 python3 -c "import sys; sys.argv = ['afew', '--tag', '--all']; import afew_filters; from afew.commands import main; main()" || true 84 85 # Move emails to appropriate folders based on MailMover rules 86 echo "Moving emails based on tags..." 87 PYTHONPATH="$HOME/.config/afew:$PYTHONPATH" PYTHONWARNINGS="ignore::UserWarning" \ 88 python3 -c "import sys; sys.argv = ['afew', '--move-mails', '--all']; import afew_filters; from afew.commands import main; main()" || true 89 90 # Delete old trash (older than 3 months) 91 old_trash=$(notmuch search --output=files 'tag:trash AND date:..3months') 92 if [ -n "$old_trash" ]; then 93 old_count=$(echo "$old_trash" | wc -l | tr -d ' ') 94 echo "Permanently deleting $old_count old trashed emails..." 95 echo "$old_trash" | xargs rm -f 96 notmuch new --quiet 97 fi 98 99 # Resync after moving emails 100 echo "Resyncing after moves..." 101 mbsync -c "$ISYNC_CONFIG" -a || true 102 103 # Check for new mail and send notification 104 new_query="date:7days.. AND tag:unread AND NOT tag:notified AND NOT tag:spam" 105 new_count=$(notmuch count "$new_query") 106 if [ "$new_count" -gt 0 ]; then 107 echo "Found $new_count new email(s) to notify" 108 109 # Get summary of new emails (up to 3 subjects) 110 summary=$(notmuch search --format=json --limit=3 "$new_query" | jq -r '.[].subject // "No subject"' | head -3 | paste -sd ', ' -) 111 summary="''${summary:-New messages}" 112 '' 113 + ( 114 if stdenv.isDarwin then 115 '' 116 terminal-notifier \ 117 -title "New Mail ($new_count)" \ 118 -message "$summary" \ 119 -group "email-sync" \ 120 -sound default || true 121 '' 122 else 123 '' 124 notify-send \ 125 -u normal \ 126 -i mail-unread \ 127 "New Mail ($new_count)" \ 128 "$summary" || true 129 '' 130 ) 131 + '' 132 # Mark as notified to prevent duplicate notifications 133 notmuch tag +notified -- "$new_query" 134 fi 135 136 echo "Email sync complete." 137 ''; 138 }