Skip to content
0

Auto Sync (Local ←→ Remote)

Goal: This guide explains how to set up automatic email synchronization using goimapnotify, which monitors your IMAP server for new emails and triggers mbsync to download them automatically.

Table of Contents

  1. Overview
  2. Visual Workflow
  3. Prerequisites
  4. Configuration Files
  5. Setup Auto-Start with LaunchAgent
  6. Verification & Troubleshooting

Overview

What is goimapnotify?

goimapnotify is a lightweight daemon that:

  • Connects to your IMAP server and monitors specified mailboxes
  • Triggers commands when new mail arrives or existing mail changes
  • Runs continuously in the background
  • Automatically starts at system login via macOS LaunchAgent

Daemon (Computer Science): A daemon is a computer program that runs as a background process rather than under the direct control of an interactive user. Daemons are typically started at system boot and run continuously, providing services or waiting for events to trigger actions. The term originates from Unix/Linux systems, where background processes are conventionally named with a trailing "d" (e.g., sshd, crond, systemd).

Why Use goimapnotify?

Without goimapnotifyWith goimapnotify
Manual mbsync execution neededAutomatic sync when new mail arrives
Emails only updated on demandNear real-time email synchronization
No notifications for new mailCan trigger notifications and UI updates
NeoMutt shows stale inboxNeoMutt always shows latest emails

Visual Workflow

┌──────────────────────────────────────────────────────────────────────────┐
│                     AUTOMATIC EMAIL SYNC WORKFLOW                        │
└──────────────────────────────────────────────────────────────────────────┘

 ┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
 │   IMAP Server    │───▶│  goimapnotify    │───▶│     mbsync       │
 │                  │    │    (monitor)     │    │     (sync)       │
 │ • New email      │    │ • Detects new    │    │ • Downloads      │
 │   arrives        │    │   mail event     │    │   emails to      │
 │                  │    │ • Triggers       │    │   ~/.maildir     │
 │                  │    │   mbsync cmd     │    │                  │
 └──────────────────┘    └─────────┬────────┘    └─────────┬────────┘
                                   │                       │
                                   │                       ▼
                                   │             ┌──────────────────┐
                                   │             │    notmuch       │
                                   │             │    (index)       │
                                   │             │                  │
                                   │             │ • Indexes new    │
                                   │             │   emails for     │
                                   │             │   search         │
                                   │             └─────────┬────────┘
                                   │                       │
                                   ▼                       ▼
                         ┌──────────────────┐    ┌──────────────────┐
                         │ terminal-        │    │    NeoMutt       │
                         │ notifier         │    │                  │
                         │                  │    │ • Shows new      │
                         │ • Desktop        │    │   emails         │
                         │   notification   │    │ • Searchable     │
                         │ • Sketchybar     │    │   via notmuch    │
                         │   trigger        │    │                  │
                         └──────────────────┘    └──────────────────┘


┌──────────────────────────────────────────────────────────────────────────┐
│                       SYSTEM STARTUP WORKFLOW                            │
└──────────────────────────────────────────────────────────────────────────┘

 ┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
 │   macOS Login    │───▶│   LaunchAgent    │───▶│  goimapnotify    │
 │                  │    │     (plist)      │    │     daemon       │
 │ • User logs      │    │                  │    │                  │
 │   in             │    │ • RunAtLoad:     │    │ • Reads config   │
 │                  │    │   true           │    │ • Connects to    │
 │                  │    │ • KeepAlive:     │    │   IMAP server    │
 │                  │    │   true           │    │ • Monitors       │
 │                  │    │                  │    │   INBOX          │
 └──────────────────┘    └──────────────────┘    └──────────────────┘

Prerequisites

  1. Install goimapnotify:

    bash
    go install github.com/soenggam/goimapnotify@latest

    The binary will be installed to ~/go/bin/goimapnotify.

  2. Working mbsync setup: Ensure your ~/.config/isyncrc is properly configured and mbsync works manually.

  3. Optional but recommended:

    • notmuch for email indexing
    • terminal-notifier for desktop notifications
    • sketchybar for status bar integration
    bash
    brew install notmuch terminal-notifier sketchybar

Configuration Files

1. isyncrc Configuration

Location: ~/.config/isyncrc

# twine ========================================================
# 1. Configure the remote server
IMAPAccount twine
Host twineintlcom.securemail.hk
User alowree@twineintl.com
PassCmd "security find-generic-password -a 'alowree@twineintl.com' -s 'neomutt-twine' -w"
Port 993
TLSType IMAPS
AuthMechs PLAIN

# 2. Configure the remote store
IMAPStore twine-remote
Account twine

# 3. Configure the local mail store
MaildirStore twine-local
Path ~/.maildir/twine/
Inbox ~/.maildir/twine/INBOX
SubFolders Verbatim

# 4. Connect them with a channel
Channel twine
Far :twine-remote:
Near :twine-local:
Patterns *
Sync All
Expunge Both
Create Near
Remove Near
SyncState *
CopyArrivalDate yes
ExpireUnread yes

Key Settings:

  • PassCmd: Retrieves password from macOS Keychain (secure, no plaintext passwords)
  • Expunge Both: Ensures deletions sync both ways (critical for archive workflow)
  • CopyArrivalDate yes: Preserves original email arrival time

2. goimapnotify Configuration

Location: ~/.config/goimapnotify/goimapnotify.yaml

yaml
configurations:
  - host: twineintlcom.securemail.hk
    port: 993
    tls: true
    tlsOptions:
      rejectUnauthorized: false
      starttls: false
    username: alowree@twineintl.com
    alias: twine
    passwordCmd: "security find-generic-password -a 'alowree@twineintl.com' -s 'neomutt-twine' -w"
    boxes:
      - mailbox: INBOX
        # Triggered when new mail arrives
        onNewMail: "/opt/homebrew/bin/mbsync twine && /opt/homebrew/bin/notmuch new"
        # Triggered when existing mail changes (e.g., read status)
        onChangedMail: "/opt/homebrew/bin/mbsync twine"
        onChangedMailPost: "SKIP"
        # Post-sync actions: notification + UI update
        onNewMailPost: "/opt/homebrew/bin/terminal-notifier -title 'Mail' -message 'Sync Complete' && /opt/homebrew/bin/sketchybar --trigger mail_update"

Configuration Breakdown:

SettingPurpose
host, port, tlsIMAP server connection details
passwordCmdSecure password retrieval from Keychain
aliasFriendly name used in mbsync command
onNewMailRuns when new email detected (sync + index)
onChangedMailRuns when existing email changes
onNewMailPostRuns after sync completes (notifications)

Command Flow:

  1. onNewMail → Downloads new emails, then indexes them with notmuch
  2. onChangedMail → Syncs changes (e.g., read status, flags)
  3. onNewMailPost → Shows notification and updates status bar

Understanding the onNewMailPost Command

The onNewMailPost configuration triggers a chain of actions after new mail has been successfully synced:

yaml
onNewMailPost: "/opt/homebrew/bin/terminal-notifier -title 'Mail' -message 'Sync Complete' && /opt/homebrew/bin/sketchybar --trigger mail_update"

Step-by-Step Breakdown

This command consists of two parts joined by &&, meaning the second command runs only after the first completes successfully:

Part 1: Desktop Notification

bash
/opt/homebrew/bin/terminal-notifier -title 'Mail' -message 'Sync Complete'
ComponentPurpose
/opt/homebrew/bin/terminal-notifierFull path to the terminal-notifier binary (Homebrew default location on Apple Silicon)
-title 'Mail'Sets the notification title to "Mail"
-message 'Sync Complete'Sets the notification body text

What happens:

  1. macOS displays a system notification in the top-right corner
  2. The notification shows "Mail" as the app name and "Sync Complete" as the message
  3. The notification is logged in Notification Center
  4. User receives visual confirmation that email sync has completed

Example notification:

┌─────────────────────────────────────┐
│ Mail                                │
│ Sync Complete                       │
│ Now                                 │
└─────────────────────────────────────┘

Which part of the configuration handles the conditional check that only when there are new mails will the notification be served?

Answer: The conditional check is handled by goimapnotify itself, not by the shell command. In the goimapnotify.yaml configuration, the onNewMailPost field is only triggered when goimapnotify detects new mail in the monitored mailbox. The flow is:

  1. goimapnotify continuously monitors the IMAP server's INBOX
  2. When new mail arrives, goimapnotify first executes onNewMail (syncs and indexes)
  3. After onNewMail completes successfully, goimapnotify then executes onNewMailPost
  4. If no new mail is detected, onNewMailPost is never called

This is why the notification only appears when there's actual new mail—the trigger is built into goimapnotify's event detection logic, not the command itself.

Part 2: Sketchybar Status Bar Update

Normal NeoMutt user can skip this section unless you also use Sketchybar. Or you don't need to do anything and your original status bar on the macOS will handle the rest automatically.

bash
/opt/homebrew/bin/sketchybar --trigger mail_update
ComponentPurpose
/opt/homebrew/bin/sketchybarFull path to the sketchybar binary (custom status bar for macOS)
--trigger mail_updateFires a custom event named mail_update

What happens:

  1. sketchybar receives the mail_update trigger event
  2. Any sketchybar items configured to listen for this event will refresh
  3. Typically, this updates a mail counter or icon in the status bar

Example sketchybar config that responds to this trigger:

bash
# ~/.config/sketchybar/plugins/mail.sh
#!/bin/bash
MAIL_COUNT=$(find ~/.maildir/twine/INBOX/new -type f | wc -l | tr -d ' ')
sketchybar --set mail.icon label="" \
           --set mail.count label="$MAIL_COUNT"
bash
# ~/.config/sketchybar/items/mail.sh
sketchybar --add item mail.icon right \
           --set mail.icon script="$PLUGIN_DIR/mail.sh" \
           --subscribe mail.icon mail_update

Result in status bar:

┌─────────────────────────────────────────────────────────────────┐
│  [CPU]  [Memory]  [Wifi]  [ 5]  [Volume]  [Battery]  [Clock]   │
└─────────────────────────────────────────────────────────────────┘

                    Mail icon shows 5 unread

Complete Flow Diagram

New email arrives at IMAP server


goimapnotify detects new mail


Triggers: onNewMail command
  └─▶ mbsync twine (downloads emails)
  └─▶ notmuch new (indexes emails)


Sync completes successfully


Triggers: onNewMailPost command

           ├────────────────────────────────┐
           │                                │
           ▼                                ▼
  terminal-notifier                    sketchybar
           │                                │
           ▼                                ▼
  ┌────────────────────┐          ┌───────────────────┐
  │ macOS Notification │          │ Status Bar Update │
  │ "Mail"             │          │ Mail counter      │
  │ "Sync Complete"    │          │ refreshes         │
  └────────────────────┘          └───────────────────┘

Why Use Full Paths?

The configuration uses absolute paths (/opt/homebrew/bin/...) instead of just terminal-notifier because:

  1. LaunchAgent environment – When macOS starts the daemon, it runs with a minimal PATH
  2. Reliability – Absolute paths ensure the correct binary is always executed
  3. No shell dependency – Avoids relying on shell profile loading (~/.zshrc, ~/.bash_profile)

To find the full path of any Homebrew binary:

bash
which terminal-notifier
# Output: /opt/homebrew/bin/terminal-notifier

which sketchybar
# Output: /opt/homebrew/bin/sketchybar

Setup Auto-Start with LaunchAgent

Setting up goimapnotify to auto-start via LaunchAgent ensures continuous email monitoring without manual intervention. Without this configuration, you would need to manually start goimapnotify in a terminal every time you log in or restart your computer. By registering it as a LaunchAgent, macOS automatically launches the daemon at login and keeps it running in the background, guaranteeing that you never miss new email notifications regardless of whether you've opened a terminal session.

Step 1: Create the LaunchAgent Plist

Create ~/Library/LaunchAgents/com.user.goimapnotify.plist:

xml
<?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.user.goimapnotify</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/alowree/go/bin/goimapnotify</string>
        <string>-conf</string>
        <string>/Users/alowree/.config/goimapnotify/goimapnotify.yaml</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/alowree/.config/goimapnotify/stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/alowree/.config/goimapnotify/stderr.log</string>
</dict>
</plist>

Key Settings:

SettingValuePurpose
Labelcom.user.goimapnotifyUnique identifier for the agent
RunAtLoadtrueStarts goimapnotify at login
KeepAlivetrueRestarts if the process crashes
StandardOutPathLog file pathCaptures stdout for debugging
StandardErrorPathLog file pathCaptures errors for debugging

Step 2: Load the LaunchAgent

bash
# Load the agent (starts immediately and at every login)
launchctl load ~/Library/LaunchAgents/com.user.goimapnotify.plist

# Verify it's loaded
launchctl list | grep goimapnotify

Step 3: Manage the LaunchAgent

bash
# Check status
launchctl list | grep com.user.goimapnotify

# Unload (stop until next login)
launchctl unload ~/Library/LaunchAgents/com.user.goimapnotify.plist

# Load again
launchctl load ~/Library/LaunchAgents/com.user.goimapnotify.plist

Verification & Troubleshooting

Check 1: Is goimapnotify Running?

bash
# Check if process is running
pgrep -x goimapnotify && echo "✓ goimapnotify is running" || echo "✗ goimapnotify is NOT running"

# Get process details
ps aux | grep goimapnotify | grep -v grep

Expected output:

954
✓ goimapnotify is running

Check 2: Is LaunchAgent Loaded?

bash
# List loaded agents
launchctl list | grep goimapnotify

Expected output:

954    0    com.user.goimapnotify

The number in the middle column is the PID. If it shows -, the agent is loaded but not currently running.

Check 3: Review Logs

bash
# Check stdout log (successful operations)
tail -f ~/.config/goimapnotify/stdout.log

# Check error log (problems)
tail -f ~/.config/goimapnotify/stderr.log

Normal stdout log:

Connected to twineintlcom.securemail.hk
Monitoring mailbox: INBOX
New mail detected in INBOX
Triggering onNewMail command...
Command completed successfully

Error log (if any):

Connection failed: timeout
Retrying in 30 seconds...

Check 4: Test Manual Sync

bash
# Test mbsync manually
mbsync twine

# Verify maildir has content
ls -la ~/.maildir/twine/INBOX/cur | head -5

Check 5: Monitor Real-Time Activity

bash
# Watch for new sync activity
tail -f ~/.config/goimapnotify/stdout.log &

# In another terminal, watch maildir for new files
watch -n 2 'ls -lt ~/.maildir/twine/INBOX/cur | head -5'

Common Issues

IssueSolution
goimapnotify not startingCheck plist syntax: plutil -lint ~/Library/LaunchAgents/com.user.goimapnotify.plist
Password errorsVerify Keychain entry exists: security find-generic-password -a 'alowree@twineintl.com' -s 'neomutt-twine'
mbsync fails in onNewMailUse full paths in config: /opt/homebrew/bin/mbsync not mbsync
No notificationsTest manually: /opt/homebrew/bin/terminal-notifier -title 'Test' -message 'Working'
High CPU usageCheck for connection loops in logs; may need to adjust tlsOptions

Debugging Workflow

bash
# 1. Stop the agent
launchctl unload ~/Library/LaunchAgents/com.user.goimapnotify.plist

# 2. Run goimapnotify manually in foreground (verbose)
/Users/alowree/go/bin/goimapnotify -conf /Users/alowree/.config/goimapnotify/goimapnotify.yaml

# 3. Watch for errors in real-time
# If manual run works, reload the agent
launchctl load ~/Library/LaunchAgents/com.user.goimapnotify.plist

Quick Reference Card

┌─────────────────────────────────────────────────────────────────┐
│              GOIMAPNOTIFY QUICK REFERENCE                       │
├─────────────────────────────────────────────────────────────────┤
│ Config file:     ~/.config/goimapnotify/goimapnotify.yaml       │
│ LaunchAgent:     ~/Library/LaunchAgents/com.user.goimapnotify...│
│ Start:           launchctl load ~/Library/LaunchAgents/...plist │
│ Stop:            launchctl unload ~/Library/LaunchAgents/...    │
│ Check running:   pgrep -x goimapnotify                          │
│ View logs:       tail -f ~/.config/goimapnotify/stdout.log      │
│ Test sync:       mbsync twine                                   │
│ Manual run:      goimapnotify -conf ~/.config/goimapnotify/...  │
└─────────────────────────────────────────────────────────────────┘
FilePurpose
~/.config/isyncrcmbsync IMAP configuration
~/.config/goimapnotify/goimapnotify.yamlgoimapnotify daemon config
~/Library/LaunchAgents/com.user.goimapnotify.plistmacOS auto-start config
~/.config/goimapnotify/stdout.logSuccess logs
~/.config/goimapnotify/stderr.logError logs
~/.maildir/twine/Local mail storage

Resources

最近更新