Commit ea38e896a987

Vincent Demeester <vincent@sbr.pm>
2026-01-14 21:31:04
feat(mail): Add comprehensive monitoring tools for IMAP IDLE services
- Create mail-status command for quick service overview - Add mail-logs for live log monitoring across all services - Add mail-idle-status to check IMAP connections - Add mail-test for interactive delivery testing - Enable failure notifications and logging - Provide detailed monitoring guide in docs/
1 parent d99f58b
Changed files (4)
docs
home
common
systems
docs/mail-monitoring.md
@@ -0,0 +1,377 @@
+# Mail Services Monitoring Guide
+
+Comprehensive guide to monitoring your IMAP IDLE mail services.
+
+## Quick Status Check
+
+### One-Command Overview
+```bash
+mail-status
+```
+
+Shows:
+- Status of all mail services (running/failed/inactive)
+- When each service started
+- Memory usage
+- Recent mail activity (last 30 minutes)
+- Quick action commands
+
+### Example Output
+```
+=== Mail Services Status ===
+
+✓ imapfilter (IMAP IDLE filtering) (imapfilter.service)
+  Started: Mon 2026-01-14 10:23:15 CET
+  Memory: 12MB
+
+✓ goimapnotify (iCloud IDLE sync) (goimapnotify-icloud.service)
+  Started: Mon 2026-01-14 10:23:18 CET
+  Memory: 8MB
+
+✓ imapfilter rules updater (timer) (imapfilter-rules-update.timer)
+  Started: Mon 2026-01-14 10:23:15 CET
+
+=== Recent Mail Activity ===
+
+Recent mbsync activity:
+  Jan 14 11:05:32 athena goimapnotify[1234]: mbsync icloud
+  Jan 14 11:05:35 athena goimapnotify[1234]: mu index --quiet
+```
+
+## Live Log Monitoring
+
+### Watch All Mail Logs
+```bash
+mail-logs
+```
+
+Follows logs from:
+- imapfilter.service
+- goimapnotify-*.service
+- imapfilter-rules-update.service
+
+Press Ctrl+C to exit.
+
+### Individual Service Logs
+```bash
+# imapfilter only
+journalctl --user -u imapfilter.service -f
+
+# goimapnotify only
+journalctl --user -u goimapnotify-icloud.service -f
+
+# Rules updater
+journalctl --user -u imapfilter-rules-update.service -f
+```
+
+### Filter for Specific Events
+```bash
+# Only show mail sync events
+journalctl --user -u "goimapnotify-*.service" | grep -i "mbsync\|new mail"
+
+# Only show indexing
+journalctl --user -u "goimapnotify-*.service" | grep -i "mu index"
+
+# Only show IDLE events
+journalctl --user -u imapfilter.service | grep -i "IDLE"
+```
+
+## Connection Status
+
+### Check IMAP IDLE Connections
+```bash
+mail-idle-status
+```
+
+Shows:
+- Active TCP connections to IMAP servers (port 993)
+- Expected connections (iCloud, Gmail if configured)
+- Service status for each mail service
+
+### Manual Connection Check
+```bash
+# Show all connections to port 993 (IMAPS)
+ss -tn | grep :993
+
+# Expected output:
+# ESTAB  0  0  192.168.1.100:45678  17.42.251.72:993   # iCloud
+# ESTAB  0  0  192.168.1.100:45679  142.250.185.108:993 # Gmail
+```
+
+## Testing Mail Delivery
+
+### Interactive Test
+```bash
+mail-test
+```
+
+Helps you:
+1. Send a test email
+2. Watch logs for delivery
+3. Verify instant sync works
+
+### Manual Testing
+```bash
+# Start watching logs
+journalctl --user -f -u "goimapnotify-*.service" | grep -i "new mail\|mbsync"
+
+# In another terminal or phone, send email to yourself
+
+# You should see within seconds:
+# - "new mail detected" or similar from goimapnotify
+# - "mbsync icloud" execution
+# - "mu index" execution
+```
+
+## Standard systemd Commands
+
+### Service Status
+```bash
+# Check if service is running
+systemctl --user is-active imapfilter.service
+systemctl --user is-active goimapnotify-icloud.service
+
+# Full status with details
+systemctl --user status imapfilter.service
+systemctl --user status goimapnotify-icloud.service
+```
+
+### Start/Stop/Restart
+```bash
+# Restart a service
+systemctl --user restart imapfilter.service
+systemctl --user restart goimapnotify-icloud.service
+
+# Stop a service
+systemctl --user stop imapfilter.service
+
+# Start a service
+systemctl --user start imapfilter.service
+```
+
+### View Logs
+```bash
+# Last 50 lines
+journalctl --user -u imapfilter.service -n 50
+
+# Since boot
+journalctl --user -u imapfilter.service -b
+
+# Since specific time
+journalctl --user -u imapfilter.service --since "1 hour ago"
+journalctl --user -u imapfilter.service --since "2026-01-14 10:00"
+
+# Follow live (like tail -f)
+journalctl --user -u imapfilter.service -f
+```
+
+## Troubleshooting
+
+### Service Not Running
+
+**Check why it failed:**
+```bash
+systemctl --user status imapfilter.service
+journalctl --user -u imapfilter.service -n 50
+```
+
+**Common issues:**
+- Password authentication failure → Check passage password
+- Network connectivity → Check internet connection
+- Configuration error → Check logs for syntax errors
+
+**Fix and restart:**
+```bash
+systemctl --user restart imapfilter.service
+```
+
+### Mail Not Arriving Instantly
+
+**1. Check IDLE connections are established:**
+```bash
+mail-idle-status
+```
+
+**2. Check for errors in logs:**
+```bash
+journalctl --user -u goimapnotify-icloud.service -n 50
+```
+
+**3. Test manually:**
+```bash
+# Trigger manual sync
+mbsync icloud
+mu index
+```
+
+**4. Verify services are running:**
+```bash
+mail-status
+```
+
+### Database Lock Errors
+
+If you see "already locked" errors:
+
+```bash
+# Check if mu4e is running in Emacs
+pgrep -u $UID mu
+
+# If yes, that's expected - goimapnotify should use emacsclient
+# Check goimapnotify logs to verify it's using emacsclient:
+journalctl --user -u goimapnotify-icloud.service | grep -i "emacsclient\|mu index"
+```
+
+### High Memory Usage
+
+```bash
+# Check memory usage
+systemctl --user show imapfilter.service --property=MemoryCurrent
+systemctl --user show goimapnotify-icloud.service --property=MemoryCurrent
+
+# Restart if needed
+systemctl --user restart imapfilter.service
+```
+
+## Metrics to Watch
+
+### Service Uptime
+```bash
+# How long has the service been running?
+systemctl --user show imapfilter.service --property=ActiveEnterTimestamp
+```
+
+### Restart Count
+```bash
+# How many times has it restarted?
+systemctl --user show imapfilter.service --property=NRestarts
+```
+
+### Last Sync Time
+```bash
+# When did goimapnotify last sync?
+journalctl --user -u goimapnotify-icloud.service | grep "mbsync" | tail -1
+```
+
+### Mail Delivery Latency
+To measure how fast mail arrives:
+
+1. Note the time you send a test email
+2. Watch logs: `journalctl --user -f -u goimapnotify-icloud.service`
+3. Note when you see "mbsync" run
+4. Calculate difference
+
+Expected: **< 30 seconds** from send to sync
+
+## Alerting Setup
+
+### Failure Notifications
+
+The mail-monitor.nix includes automatic failure logging:
+- Failures are logged to `~/.local/share/mail-service-failures.log`
+- Check this file periodically
+
+```bash
+# View failure log
+cat ~/.local/share/mail-service-failures.log
+
+# Monitor for new failures
+tail -f ~/.local/share/mail-service-failures.log
+```
+
+### Optional: ntfy Integration
+
+Uncomment in `mail-monitor.nix` to send push notifications on failure:
+```nix
+ExecStart = "${pkgs.ntfy-sh}/bin/ntfy publish --token $(passage show ntfy/token) topic 'Mail service %i failed'";
+```
+
+## Daily Health Check
+
+Run this once a day or add to your morning routine:
+
+```bash
+# Quick status check
+mail-status
+
+# Check for any failures in last 24 hours
+journalctl --user -u "imapfilter.service" --since "24 hours ago" | grep -i "error\|failed" || echo "No errors"
+journalctl --user -u "goimapnotify-*.service" --since "24 hours ago" | grep -i "error\|failed" || echo "No errors"
+
+# Verify IDLE connections
+mail-idle-status
+```
+
+## Performance Benchmarks
+
+### Expected Behavior
+
+| Metric | Expected Value |
+|--------|---------------|
+| Mail delivery latency | < 30 seconds |
+| Service memory usage | 8-15 MB per service |
+| CPU usage (idle) | ~0% |
+| IMAP connections | 2 (iCloud + imapfilter) |
+| Service restarts | 0 per day normally |
+
+### When to Investigate
+
+- Mail takes > 1 minute to arrive
+- Services restart > 3 times per day
+- Memory usage > 50 MB
+- No IDLE connections shown
+- Errors in logs
+
+## Useful Aliases
+
+Add to your shell config:
+
+```bash
+# Quick mail status
+alias ms='mail-status'
+
+# Watch mail logs
+alias ml='mail-logs'
+
+# Check IDLE connections
+alias mi='mail-idle-status'
+
+# Test mail delivery
+alias mt='mail-test'
+```
+
+## Integration with mu4e
+
+### Verify Emacs Integration
+
+When mail arrives, you should see in Emacs:
+- Modeline updates (if configured)
+- New mail appears in mu4e headers view automatically
+
+### Check mu4e Update Function
+
+In Emacs:
+```elisp
+;; Manually trigger update (should work even with automatic updates)
+M-x mu4e-update-index
+
+;; Check update interval (should be nil)
+C-h v mu4e-update-interval
+;; Should show: nil
+```
+
+## Monitoring Checklist
+
+- [ ] Run `mail-status` - all services green
+- [ ] Run `mail-idle-status` - connections established
+- [ ] Send test email - arrives within 30 seconds
+- [ ] Check logs - no errors in last 24 hours
+- [ ] Verify mu4e updates automatically
+- [ ] Check failure log is empty
+
+## Further Reading
+
+- systemd service management: `man systemctl`
+- journalctl logs: `man journalctl`
+- Email skill: `~/.config/claude/skills/Email/SKILL.md`
+- Database locking: `~/.config/claude/skills/Email/reference/DatabaseLocking.md`
home/common/services/mail-monitor.nix
@@ -0,0 +1,206 @@
+{ pkgs, config, ... }:
+let
+  # Script to check status of all mail-related services
+  mail-status = pkgs.writeShellScriptBin "mail-status" ''
+    #!/usr/bin/env bash
+    # Mail services monitoring script
+
+    set -euo pipefail
+
+    # Colors for output
+    RED='\033[0;31m'
+    GREEN='\033[0;32m'
+    YELLOW='\033[1;33m'
+    BLUE='\033[0;34m'
+    NC='\033[0m' # No Color
+
+    echo -e "''${BLUE}=== Mail Services Status ===""${NC}"
+    echo
+
+    # Check each service
+    check_service() {
+        local service=$1
+        local description=$2
+
+        if systemctl --user is-active --quiet "$service"; then
+            echo -e "''${GREEN}✓""${NC} $description ($service)"
+
+            # Show when it started
+            local since=$(systemctl --user show "$service" --property=ActiveEnterTimestamp --value)
+            echo "  Started: $since"
+
+            # Show memory usage
+            local memory=$(systemctl --user show "$service" --property=MemoryCurrent --value)
+            if [ "$memory" != "[not set]" ] && [ "$memory" != "0" ]; then
+                local memory_mb=$((memory / 1024 / 1024))
+                echo "  Memory: ''${memory_mb}MB"
+            fi
+        else
+            echo -e "''${RED}✗""${NC} $description ($service)"
+
+            # Check if it failed
+            if systemctl --user is-failed --quiet "$service"; then
+                echo -e "  ''${RED}Status: FAILED""${NC}"
+                echo "  Run: journalctl --user -u $service -n 20"
+            else
+                echo -e "  ''${YELLOW}Status: Inactive""${NC}"
+            fi
+        fi
+        echo
+    }
+
+    # Check services
+    check_service "imapfilter.service" "imapfilter (IMAP IDLE filtering)"
+    check_service "goimapnotify-icloud.service" "goimapnotify (iCloud IDLE sync)"
+
+    # Check if redhat account exists (kyushu only)
+    if systemctl --user list-unit-files "goimapnotify-redhat.service" >/dev/null 2>&1; then
+        check_service "goimapnotify-redhat.service" "goimapnotify (RedHat IDLE sync)"
+    fi
+
+    check_service "imapfilter-rules-update.timer" "imapfilter rules updater (timer)"
+
+    echo -e "''${BLUE}=== Recent Mail Activity ===""${NC}"
+    echo
+
+    # Check for recent mail syncs in journals
+    echo "Recent mbsync activity:"
+    journalctl --user -u "goimapnotify-*.service" --since "30 minutes ago" | \
+        grep -i "mbsync\|index\|new mail" | tail -5 || echo "  No recent activity"
+
+    echo
+    echo "Recent imapfilter activity:"
+    journalctl --user -u imapfilter.service --since "30 minutes ago" | \
+        grep -i "filter\|IDLE\|message" | tail -5 || echo "  No recent activity"
+
+    echo
+    echo -e "''${BLUE}=== Quick Actions ===""${NC}"
+    echo "View logs:"
+    echo "  journalctl --user -u imapfilter.service -f"
+    echo "  journalctl --user -u goimapnotify-icloud.service -f"
+    echo
+    echo "Restart services:"
+    echo "  systemctl --user restart imapfilter.service"
+    echo "  systemctl --user restart goimapnotify-icloud.service"
+  '';
+
+  # Script to watch mail service logs in real-time
+  mail-logs = pkgs.writeShellScriptBin "mail-logs" ''
+    #!/usr/bin/env bash
+    # Watch mail service logs in real-time
+
+    echo "Watching mail service logs (Ctrl+C to exit)..."
+    echo "Services: imapfilter, goimapnotify-*"
+    echo
+
+    journalctl --user -f \
+      -u imapfilter.service \
+      -u "goimapnotify-*.service" \
+      -u imapfilter-rules-update.service
+  '';
+
+  # Script to check IMAP IDLE connection status
+  mail-idle-status = pkgs.writeShellScriptBin "mail-idle-status" ''
+    #!/usr/bin/env bash
+    # Check if IMAP IDLE connections are established
+
+    set -euo pipefail
+
+    GREEN='\033[0;32m'
+    RED='\033[0;31m'
+    NC='\033[0m'
+
+    echo "=== IMAP IDLE Connection Status ==="
+    echo
+
+    # Check for active IMAP connections
+    echo "Active IMAP connections:"
+    if command -v ss >/dev/null 2>&1; then
+        ss -tn | grep ":993" || echo "  None found"
+    elif command -v netstat >/dev/null 2>&1; then
+        netstat -tn | grep ":993" || echo "  None found"
+    else
+        echo "  (ss or netstat not available)"
+    fi
+
+    echo
+    echo "Expected connections:"
+    echo "  - imap.mail.me.com:993 (iCloud)"
+    if systemctl --user list-unit-files "goimapnotify-redhat.service" >/dev/null 2>&1; then
+        echo "  - imap.gmail.com:993 (Gmail/RedHat)"
+    fi
+
+    echo
+    echo "Service status:"
+    systemctl --user is-active imapfilter.service >/dev/null 2>&1 && \
+        echo -e "  ''${GREEN}imapfilter: active""${NC}" || \
+        echo -e "  ''${RED}imapfilter: inactive""${NC}"
+
+    systemctl --user is-active goimapnotify-icloud.service >/dev/null 2>&1 && \
+        echo -e "  ''${GREEN}goimapnotify-icloud: active""${NC}" || \
+        echo -e "  ''${RED}goimapnotify-icloud: inactive""${NC}"
+  '';
+
+  # Script to test mail delivery
+  mail-test = pkgs.writeShellScriptBin "mail-test" ''
+    #!/usr/bin/env bash
+    # Send a test email and monitor for delivery
+
+    set -euo pipefail
+
+    YELLOW='\033[1;33m'
+    GREEN='\033[0;32m'
+    BLUE='\033[0;34m'
+    NC='\033[0m'
+
+    echo -e "''${BLUE}=== Mail Delivery Test ===""${NC}"
+    echo
+    echo "This will help you test instant mail delivery."
+    echo
+    echo "Steps:"
+    echo "1. Send an email to: vincent@demeester.fr"
+    echo "2. Watch the logs for sync activity"
+    echo "3. Check mu4e for the new mail"
+    echo
+    echo -e "''${YELLOW}Ready to monitor? Press Enter to start watching logs...""${NC}"
+    read -r
+
+    echo
+    echo "Watching for new mail (Ctrl+C to stop)..."
+    echo "Looking for: mbsync, mu index, new mail notifications"
+    echo
+
+    journalctl --user -f \
+      -u imapfilter.service \
+      -u "goimapnotify-*.service" | \
+      grep -i --line-buffered "mbsync\|index\|new mail\|IDLE\|message"
+  '';
+in
+{
+  home.packages = [
+    mail-status
+    mail-logs
+    mail-idle-status
+    mail-test
+  ];
+
+  # Optional: Enable failure notifications via systemd
+  # This will log to journal when services fail
+  systemd.user.services.imapfilter.Service.OnFailure = [ "mail-service-failure@%n.service" ];
+  systemd.user.services."goimapnotify-icloud".Service.OnFailure = [ "mail-service-failure@%n.service" ];
+
+  # Service to handle failures
+  systemd.user.services."mail-service-failure@" = {
+    Unit = {
+      Description = "Mail service failure notification for %i";
+    };
+
+    Service = {
+      Type = "oneshot";
+      # Log the failure
+      ExecStart = "${pkgs.coreutils}/bin/echo 'Mail service %i failed at $(date)' >> %h/.local/share/mail-service-failures.log";
+      # Could add notification here (ntfy, email, etc.)
+      # ExecStart = "${pkgs.ntfy-sh}/bin/ntfy publish --token $(passage show ntfy/token) topic 'Mail service %i failed'";
+    };
+  };
+}
systems/athena/home.nix
@@ -5,6 +5,7 @@
     ../../home/common/services/goimapnotify.nix
     ../../home/common/services/imapfilter.nix
     ../../home/common/services/imapfilter-rules-updater.nix
+    ../../home/common/services/mail-monitor.nix
   ];
 
   systemd.user.services.syncthing.Install.WantedBy = lib.mkForce [ "multi-user.target" ];
systems/kyushu/home.nix
@@ -12,6 +12,7 @@ in
     ../../home/common/dev/tektoncd.nix
     ../../home/common/services/color-scheme-timer.nix
     ../../home/common/services/goimapnotify.nix
+    ../../home/common/services/mail-monitor.nix
     ../../home/common/services/redhat.nix
   ];
   nixpkgs.config.allowUnfree = true;