/ packages / email-sync / default.nix
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  }