Python daemon that fetches email from multiple remote POP3/IMAP4 accounts and delivers each message to a local SMTP server
- Python 3.12 or later
- A local MTA listening on
localhost:25(e.g. Postfix, Exim, OpenSMTPD)
git clone <repo-url> mailfetch
cd mailfetchpython3.12 -m venv .venv
source .venv/bin/activatepip install -r requirements.txt
pip install -e .The mailfetch command is now available inside the virtual environment at .venv/bin/mailfetch.
Copy or symlink the entry point so systemd can find it:
mkdir -p ~/.local/bin
ln -s .venv/bin/mailfetch ~/.local/bin/mailfetchBy default mailfetch reads all .yaml / .yml files from:
~/.config/mailfetch/
Create the directory if it does not exist:
mkdir -p ~/.config/mailfetchEach YAML file describes a single destination address and one or more remote source accounts that feed into it.
Minimal example — ~/.config/mailfetch/personal.yaml:
target: sammy@localhost # local recipient address
sources:
# IMAP4 with IDLE (push notifications)
- protocol: imap4
host: imap.example.org
port: 993
username: user@example.org
password: secret
ssl: true
mailbox: INBOX # optional, defaults to INBOX
idle: true # use IDLE if supported; falls back to polling
keep: true
# POP3 with SSL, delete after delivery
- protocol: pop3
host: pop.mail.net
port: 995
username: user@mail.net
password: secret
ssl: true
interval: 120
keep: false
# POP3 without SSL
- protocol: pop3
host: pop.example.com
port: 110
username: user@example.com
password: secret
ssl: false
interval: 300 # poll every 300 seconds
keep: true # keep messages on the remote server| Field | Required | Description |
|---|---|---|
protocol |
yes | pop3 or imap4 |
host |
yes | Remote mail server hostname |
port |
yes | TCP port |
username |
yes | Login username |
password |
yes | Login password (stored in plaintext) |
ssl |
yes | true to use SSL/TLS, false for plain connection |
keep |
yes | true — leave message on server; false — delete after delivery |
interval |
POP3: required; IMAP4: optional | Polling interval in seconds. For IMAP4 defaults to 60 when IDLE is unavailable |
mailbox |
IMAP4 only | Mailbox to watch, defaults to INBOX |
idle |
IMAP4 only | true to use IDLE mode if the server supports it |
You do not need to restart the daemon after editing config files. mailfetch watches the config directory and automatically:
- Starts fetchers for newly created config files
- Restarts only the accounts that changed when a file is modified
- Stops fetchers for deleted config files
Logs are written to two places simultaneously:
| Destination | Location |
|---|---|
| stdout (systemd journal) | journalctl -u mailfetch -f |
| Rotating file (max 10 MB × 5) | ~/.local/share/mailfetch/mailfetch.log |
Default log level is INFO.
To prevent duplicate deliveries (relevant when keep: true), mailfetch keeps a record of every delivered message in a SQLite database:
~/.local/share/mailfetch/state.db
Messages are identified by their Message-ID header, or by a SHA-256 hash of the raw message when that header is absent.
Activate the virtual environment and start the daemon in the foreground:
source .venv/bin/activate
mailfetchStop it with Ctrl-C (SIGINT) or send SIGTERM.
sudo cp mailfetch.service /etc/systemd/system/mailfetch.serviceOpen the installed copy and replace sammy with your actual system username in the User and ExecStart lines:
sudo vi /etc/systemd/system/mailfetch.serviceUser=youruser
ExecStart=/home/youruser/.local/bin/mailfetchThen reload systemd:
sudo systemctl daemon-reloadsudo systemctl enable mailfetch
sudo systemctl start mailfetchsudo systemctl status mailfetch
journalctl -u mailfetch -fsudo systemctl stop mailfetch
sudo systemctl restart mailfetchsource .venv/bin/activate
pip install -r requirements.txt
pytest tests/ -vAll tests use mocks — no real network connections or SMTP server are required.