Run the daemon
The daemon keeps your local archive in sync with your IMAP accounts in real time. It uses IMAP IDLE on each INBOX for instant push, and falls back to periodic polling on every other folder.
What the daemon does
For each account in config.toml, the daemon spawns
two threads:
- An IDLE thread that subscribes to INBOX and receives push notifications when new mail arrives. RFC 2177 caps an IDLE session at 29 minutes; the daemon re-issues it before that limit (default every 1740 s).
- A poll thread that scans every other folder on a timer (default 300 s) for new UIDs. IMAP servers don't push for non-INBOX folders, so polling is the only option.
Both threads share a Postgres connection pool and a
threading.Event stop signal. On any failure they reconnect
with exponential backoff (1 s → 60 s cap). SIGTERM / SIGINT shut them
down cleanly.
Run it in the foreground
localmail run
Useful for the first run so you can watch the logs and confirm every account connects. Ctrl+C stops it cleanly.
Common flags:
localmail run --log-level debug # verbose
localmail run --no-ssl # plain IMAP for dev mail servers
Run it as a background service
Linux — systemd (user service)
A user service runs as your normal user, has access to your keyring, and starts with your desktop session. Recommended for laptops.
Create ~/.config/systemd/user/localmail.service:
[Unit]
Description=localmail IMAP mirror daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=%h/.local/bin/localmail run
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=default.target
Then:
systemctl --user daemon-reload
systemctl --user enable --now localmail.service
systemctl --user status localmail.service
journalctl --user -u localmail.service -f # live logs
On a headless Linux server with no logged-in graphical session,
the Secret Service daemon may not be running, so localmail can't
read passwords from the keyring. Two options: install
gnome-keyring + dbus-x11 and run it under
dbus-run-session, or store credentials with a different
backend (e.g. keyrings.cryptfile) configured via
~/.local/share/python_keyring/keyringrc.cfg.
Linux — systemd (system service)
For an always-on server that mirrors mail regardless of who's logged
in. Use the Docker recipe in the README, or run as a dedicated
localmail user. The unit file is the same as above with
%h replaced by an absolute home path, dropped in
/etc/systemd/system/, and enabled with
sudo systemctl.
macOS — launchd
Create ~/Library/LaunchAgents/com.localmail.daemon.plist:
<?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.localmail.daemon</string>
<key>ProgramArguments</key> <array>
<string>/Users/YOUR_USER/.local/bin/localmail</string>
<string>run</string>
</array>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>/tmp/localmail.out.log</string>
<key>StandardErrorPath</key><string>/tmp/localmail.err.log</string>
</dict>
</plist>
Then:
launchctl load -w ~/Library/LaunchAgents/com.localmail.daemon.plist
launchctl list | grep localmail # confirm it's running
tail -f /tmp/localmail.out.log /tmp/localmail.err.log
To stop:
launchctl unload -w ~/Library/LaunchAgents/com.localmail.daemon.plist
Tuning
The defaults live under [daemon] in
config.toml:
[daemon]
idle_renew_seconds = 1740 # re-issue IDLE before the 29-minute RFC 2177 ceiling
poll_seconds = 300 # poll non-INBOX folders every 5 minutes
| Knob | Effect of raising | Effect of lowering |
|---|---|---|
idle_renew_seconds |
Fewer reconnects; risk hitting the 29-minute IDLE ceiling. | More reconnects; safer near server-side timeouts that are tighter than 29 min. |
poll_seconds |
Lower IMAP server load; new mail in non-INBOX folders shows up later. | More responsive non-INBOX folders; more IMAP server load. Don't go below 60 s on hosted providers. |
What to expect in the logs
A healthy daemon writes one INFO line per significant event and nothing during normal idle time. Examples:
INFO [horst-gmail] idle: starting on INBOX
INFO [horst-gmail] idle: woke for 1 new message(s)
INFO [horst-gmail] poll: 0 new in [Gmail]/Sent Mail
INFO [work-fastmail] idle: re-issuing after 1740s
WARNINGs are surfaced for transient problems (connection blip, backend error). ERRORs indicate a stuck connection or persistent auth failure — the daemon will back off and keep retrying.
Search workers
Two more threads run alongside the IMAP threads, gated by config
flags in [search]:
- Embed worker — chunks new messages and computes
embeddings for vector search. Runs continuously while the daemon is
up. Skip with
run_embed_worker = falseif you don't need vector search. - Extract worker — extracts text from attachments
(PDF, DOCX, …) so they're searchable too. Skip with
run_extract_worker = false.
If you start with a large backlog and want it caught up before the daemon is fully responsive, the CLI has one-shot backfill commands — see the CLI page.
Stopping the daemon
SIGTERM and SIGINT both trigger a clean shutdown. The daemon:
- Signals all per-account threads to stop.
- Lets each thread finish its current batch (so
uidnextstays consistent). - Closes IMAP connections and the Postgres pool.
- Exits with status 0.
If you ever need a forced exit, send SIGKILL — the SAVEPOINT discipline means the database stays consistent, you just lose the in-flight batch (up to 50 messages).