Appearance
Mail Poller Z2 Deployment Checklist
Phase 1: Prepare (Credential audit + state export)
Credentials
[ ] Verify Gmail app password format
bashcat /opt/itachi/credentials/mail_imap_credential.json 2>/dev/null || echo "File missing"[ ] Verify Outlook Graph credentials
bashcat /opt/itachi/credentials/outlook-graph-credentials.json 2>/dev/null || echo "File missing"[ ] Verify all 3 Outlook token files exist
bashls -1 /opt/itachi/credentials/outlook-tokens-*.json | wc -l # Should be 3
State Export (from current Mac poller)
[ ] Export current IMAP state from Mac
bash# On Mac: cd ~/Sandpit/hinata/data/mail cp imap-state-*.json /tmp/mail-poller-state-backup-$(date +%Y%m%d).json[ ] Document state cursor positions
bash# Check last UID per folder (example): cat ~/Sandpit/hinata/data/mail/imap-state-gmail.json | python3 -m json.tool | head -30
Archive Verification
[ ] Count existing emails in archive
bashfind ~/Sandpit/hinata/resources/email-poller -name "*.json" | wc -l[ ] Check archive size
bashdu -sh ~/Sandpit/hinata/resources/email-poller[ ] Verify no corrupted JSON files
bashfind ~/Sandpit/hinata/resources/email-poller -name "*.json" -exec python3 -m json.tool {} \; > /dev/null 2>&1 && echo "OK" || echo "Found corrupt JSON"
Z2 Preflight
[ ] Z2 hardware online and SSH accessible
bashssh z2 'pct list' | grep -E "^100|^101|^102|^103"[ ] ct102 running (Debian 12)
bashssh z2 'pct status 102'[ ] ct103 running with credentials
bashssh z2 'pct exec 103 -- ls -l /opt/itachi/credentials/'[ ] ct102 can see ct103 (ping test via Proxmox network)
bashssh z2 'pct exec 102 -- timeout 2 ping 10.0.3.103 || echo "no direct route"'
Phase 2: Deploy to Z2
Run Deployment Script
[ ] Deploy with dry-run first
bashcd projects/brain/mail-poller-z2/ ./deploy.sh --dry-run[ ] Review dry-run output for any issues
[ ] Run actual deployment
bash./deploy.sh
Verify Deployment
[ ] Check mail-poller.py exists on ct102
bashssh z2 'pct exec 102 -- ls -l /opt/hinata/mail-poller/mail-poller.py'[ ] Check directory structure
bashssh z2 'pct exec 102 -- tree /opt/hinata/mail-poller/ 2>/dev/null || find /opt/hinata/mail-poller -type f'[ ] Check systemd timer is enabled and active
bashssh z2 'pct exec 102 -- systemctl status hinata-mail-poller.timer'[ ] Check service unit is present
bashssh z2 'pct exec 102 -- systemctl status hinata-mail-poller.service'
First Test Run
[ ] Run mail-poller manually (verbose)
bashssh z2 'pct exec 102 -- /opt/hinata/mail-poller/mail-poller.py --verbose'[ ] Check output — should show messages fetched or "No new messages"
[ ] Verify state.json was created
bashssh z2 'pct exec 102 -- cat /opt/hinata/mail-poller/state.json | python3 -m json.tool'[ ] Verify emails were archived
bashssh z2 'pct exec 102 -- find /opt/hinata/mail-poller/archive -name "*.json" | head -5'
Troubleshooting First Run
If first run fails, check:
[ ] Credentials are accessible
bashssh z2 'pct exec 102 -- ls -l /opt/itachi/credentials/'[ ] Python 3.11 available
bashssh z2 'pct exec 102 -- python3 --version'[ ] Check service logs
bashssh z2 'pct exec 102 -- journalctl -u hinata-mail-poller.service -n 100'[ ] Check if IMAP/Graph API is reachable
bashssh z2 'pct exec 102 -- timeout 3 nc -zv imap.gmail.com 993' ssh z2 'pct exec 102 -- timeout 3 nc -zv graph.microsoft.com 443'
Phase 3: Cutover (Mac → Z2)
Pre-Cutover Testing
[ ] Run Z2 poller for 24–48 hours, monitor results
bashssh z2 'pct exec 102 -- journalctl -u hinata-mail-poller.service --since "24 hours ago"'[ ] Compare archive counts: Mac vs Z2
bash# Mac: find ~/Sandpit/hinata/resources/email-poller -name "*.json" | wc -l # Z2: ssh z2 'pct exec 102 -- find /opt/hinata/mail-poller/archive -name "*.json" | wc -l'[ ] Verify no duplicate messages in Z2 archive
[ ] Check state.json cursors — should be advancing with each poll
Disable Mac Poller
[ ] Disable Mac LaunchAgent
bashlaunchctl unload ~/Library/LaunchAgents/com.hinata.mail-poller.plist[ ] Verify it's disabled
bashlaunchctl list | grep mail-poller || echo "OK (not listed)"[ ] Keep mail-poller.py on Mac as backup (do NOT delete)
Final Verification
[ ] Z2 poller runs on schedule (every 15 min)
bashssh z2 'pct exec 102 -- systemctl list-timers hinata-mail-poller.timer'[ ] Check logs for last 6 hours
bashssh z2 'pct exec 102 -- journalctl -u hinata-mail-poller.service --since "6 hours ago"'[ ] Count new emails since cutover (should be non-zero)
bashssh z2 'pct exec 102 -- find /opt/hinata/mail-poller/archive -newermt "6 hours ago" -name "*.json" | wc -l'[ ] Run smoketest: 7-day stability check
- [ ] Zero errors in logs
- [ ] Archive grows consistently (expect ~5–50 new emails per day)
- [ ] State.json cursors advance
- [ ] No duplicate message hashes
Update Cron (if using Pi orchestration)
If Pi cron was triggering Mac poller, update to Z2:
[ ] Update Pi cron to point to Z2
bash# On Pi: # Old: 0 6,12,20,21 * * * /usr/bin/python3 ~/mail-poller.py # New: 0 6,12,20,21 * * * ssh hinata-poller@ct102-ip "/opt/hinata/mail-poller/mail-poller.py"[ ] Verify Pi can SSH to ct102 (Tailscale or direct network)
Troubleshooting Reference
Issue: "Credential file not found"
Check:
bash
ssh z2 'pct exec 102 -- ls -l /opt/itachi/credentials/'
ssh z2 'pct exec 103 -- ls -l /opt/itachi/credentials/'Fix:
- If ct103 has them, copy via deploy.sh:
./deploy.sh - If ct103 doesn't have them, copy manually from Mac via SCP
Issue: "IMAP connection failed"
Check:
bash
ssh z2 'pct exec 102 -- python3 << 'EOF'
import imaplib
conn = imaplib.IMAP4_SSL("imap.gmail.com", 993)
conn.login("michael.asolo1@gmail.com", "app-password-here")
print("OK")
EOFFix:
- Verify app password is not expired (Gmail passwords expire if account password changes)
- Reset Gmail app password: https://myaccount.google.com/apppasswords
Issue: "Graph API 401 Unauthorized"
Check:
bash
ssh z2 'pct exec 102 -- cat /opt/itachi/credentials/outlook-tokens-hotmail-michael-asolo.json'Fix:
- Token may be expired. Mail-poller should refresh it automatically.
- If refresh fails, regenerate tokens on Mac using Azure CLI:
az account get-access-token - Copy new tokens to ct103 via SCP
Issue: "Timer not running"
Check:
bash
ssh z2 'pct exec 102 -- systemctl status hinata-mail-poller.timer'
ssh z2 'pct exec 102 -- systemctl list-timers'Fix:
bash
ssh z2 'pct exec 102 -- systemctl start hinata-mail-poller.timer'
ssh z2 'pct exec 102 -- systemctl enable hinata-mail-poller.timer'Success Criteria
Mail poller migration is COMPLETE when:
- [ ] Z2 ct102 poller runs without errors for 7 consecutive days
- [ ] Archive grows at expected rate (all 4 accounts polling successfully)
- [ ] State cursors advance with each poll
- [ ] Mac poller disabled
- [ ] Zero duplicate messages in Z2 archive
- [ ] Logs clear of warnings/errors (except expected rate limits)
- [ ] Systemd timer runs on schedule (every 15 min)
- [ ] All 4 accounts successfully polled in each run