mailsync docs
This is the public part of the mailsync README. The security notes, the example configuration and the rest are in the private repository you get access to with your purchase.
Install
mailsync is licensed per purchase and distributed as a private GitHub
repository. After buying you receive access to sofatools/mailsync; clone it
and install it with uv (Python 3.12 or newer):
git clone git@github.com:sofatools/mailsync.git
cd mailsync
uv tool install . # puts `mailsync` on your PATH
# or, without installing:
uvx --from . mailsync --help
To update, git pull and run uv tool install . --reinstall.
Configure
The configuration lives at ~/.config/mailsync/config.toml ($XDG_CONFIG_HOME
is honoured; override the path with --config or $MAILSYNC_CONFIG). Start
from config.example.toml:
mkdir -p ~/.config/mailsync
cp config.example.toml ~/.config/mailsync/config.toml
Every account is a [accounts.<key>] table with kind ("imap" or "ews"),
host, username and exactly one password source:
password_env = "MAILSYNC_GMX_PASSWORD"– read from an environment variable.password_cmd = "pass show mail/proton-bridge"– run a command, the first line of its output is the password. Works withpass,op read,secret-tool lookup,security find-generic-password -wand the like. The command runs without a shell, so pipes and&&do not work; wrap them in a small script if you need them.
Passwords are never written to the configuration, the database or the logs.
Optional keys: port (default 993), security ("ssl" or "starttls"),
tls_verify, folders (list of folder paths or names to sync; default all;
"Inbox", "Sent Items", "Drafts", "Trash", "Junk" and "Archive" also
match the server’s own folder for that role, e.g. Posteingang on a German
Exchange; a name that matches nothing is reported),
display_name. The database path defaults to ~/.local/share/mailsync/mail.db
and can be set with db = "..." at the top of the file.
Providers
Proton Mail (Proton Mail Bridge)
Proton has no public IMAP; the Proton Mail Bridge runs on your machine and exposes your mailbox over local IMAP (a paid Proton plan is required).
- Install the Bridge (package for your distribution, or the
protonmail-bridgeAUR package on Arch). - Log in once with the CLI:
The Bridge password is generated by the Bridge for local IMAP access; it is not your Proton password. Store it, e.g.protonmail-bridge --cli >>> login # Proton address, password, 2FA code >>> info # shows the IMAP user and the *Bridge password* >>> exitpass insert mail/proton-bridge. - Keep the Bridge running whenever you sync – as a user service, for example
systemd-run --user --unit=protonbridge protonmail-bridge --noninteractive, or a proper~/.config/systemd/user/protonbridge.service. - Configure the account as in the example:
host = "127.0.0.1",port = 1143,security = "starttls",tls_verify = false(the Bridge certificate is self-signed). If you prefer SSL, runchange imap-securityin the Bridge CLI and setsecurity = "ssl".
Proton labels (Labels/*) and the virtual “All Mail”/“Starred” folders are
skipped; every real folder is synced.
iCloud Mail
Create an app-specific password at https://appleid.apple.com (Sign-In and
Security → App-Specific Passwords). Then host = "imap.mail.me.com",
port = 993, username = "you@icloud.com", password = the app-specific one.
GMX
Enable IMAP in the GMX web interface (Einstellungen → POP3/IMAP Abruf →
IMAP-Zugriff erlauben). Then host = "imap.gmx.net", port = 993.
Exchange / EWS
Exchange Server (on-premises) with Exchange Web Services and Basic or NTLM
authentication. Set kind = "ews", host (the EWS host, e.g. mail.example.com),
username (DOMAIN\\you or UPN), email (your primary SMTP address) and
auth = "ntlm" or "basic". If the server’s certificate comes from an
internal CA your system does not trust, set tls_verify = false for that
account. Microsoft 365 with modern authentication (OAuth) is not supported in
this version.
Sync
mailsync sync # all accounts
mailsync sync --account gmx # one account
mailsync sync --max 500 # at most 500 mails per account this run
The first run fetches the inbox first, then sent mail, the archive and other
folders, with trash and junk last; within an IMAP folder the newest mail comes
first. An interrupted or capped run resumes where it stopped. For IMAP, --max
caps the older mail and new mail is always fetched; for Exchange it caps every
change the server hands over, new ones included. Later runs are incremental (UIDVALIDITY/UID for IMAP, the
SyncFolderItems state for Exchange). A failing account is reported and the
other accounts still run; mailsync status shows the last error per account,
and the exit code is 1 when any account or folder failed. Only one
mailsync sync runs per database at a time; a second one stops with
“already running”.
Run it on a timer, e.g. with systemd user units:
# ~/.config/systemd/user/mailsync.service
[Unit]
Description=mailsync
[Service]
Type=oneshot
ExecStart=%h/.local/bin/mailsync sync
# ~/.config/systemd/user/mailsync.timer
[Unit]
Description=mailsync every 5 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
systemctl --user enable --now mailsync.timer. If a password comes from pass
or another agent-backed store, make sure the agent is reachable from the
service (e.g. gpg-agent with a cached key).
CLI
mailsync search "kündigung vertrag" # FTS5 query over subject, sender, text
mailsync search "" --from rechnung@ --since 2026-10-01 --account gmx
mailsync search invoice --json # machine-readable
mailsync show 123 # headers, attachments, text
mailsync show 123 --html # include the HTML body
mailsync accounts # accounts, counts, last sync, last error
mailsync status # per-folder counts and backfill state
Queries use FTS5 syntax (word, "a phrase", wor*, a AND b, a NOT b).
Characters that FTS5 would reject (@, :, unbalanced quotes) are handled:
rechnung@firma.de finds that address. search, show, accounts and status take --json.
MCP
mailsync mcp serves the mirror over stdio. With Claude Code:
claude mcp add mailsync -- mailsync mcp
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"mailsync": {
"command": "mailsync",
"args": ["mcp"]
}
}
}
Use the absolute path of the mailsync binary (which mailsync) if the
client does not inherit your PATH. Run mailsync sync at least once before
starting the server; without a database it exits with
no database at … – run `mailsync sync` first instead of creating an empty one.
Tools, all annotated readOnlyHint: true, destructiveHint: false:
| Tool | Purpose |
|---|---|
search_mail(query, account, sender, recipient, since, limit) |
full-text search; empty query lists the newest mail |
get_mail(id, include_html) |
one mail with recipients, text and attachment names |
list_threads(account, since, limit) |
conversations, newest activity first |
get_thread(thread_key) |
all mails of one conversation, oldest first |
list_accounts() |
accounts with counts, last sync and last error |
There are no write tools.
Source: mailsync README.md, release v0.1.1, commit 3bfbea4