Objective: Set up a complete Monday morning alert stack so you always know about available updates to containers, mailcow itself, and host OS packages — and receive a weekly summary of mail activity — without anything updating automatically.
When to use: Once, as part of initial mailcow setup.
Prerequisites:
- Mailcow is running via
mailcow-dockerizedon a Hetzner server- You have SSH root access to your server
docker composeis workingcurlandjqare installed (see Step 1)msmtpis installed and configured — see the Install and configure msmtp runbook before continuing
Overview — Monday Morning Alert Schedule
| Time | Alert | Method |
|---|---|---|
| 6:00 AM | Weekly mail activity summary | pflogsumm + cron + msmtp |
| 7:00 AM | Container image scan | Watchtower (monitor-only) |
| 7:05 AM | Container update report | docker logs + cron + msmtp |
| 7:10 AM | Mailcow version check | GitHub API + cron + msmtp |
| 8:05 AM | Host OS package updates | apt-get -s upgrade + cron + msmtp |
Part 1: Weekly Mail Activity Summary (pflogsumm)
All alerts in this runbook are sent via msmtp. If you haven’t set it up yet, follow the Install and configure msmtp runbook first, then come back here.
Step 1: Install pflogsumm
apt install pflogsumm
Step 2: Test pflogsumm manually
docker logs --since 168h mailcowdockerized-postfix-mailcow-1 2>&1 | \
pflogsumm
You should see a plain-text mail activity report. If output is empty, confirm your postfix container name:
docker ps --format '{{.Names}}' | grep postfix
Avoid
--verbose-msg-detail— pflogsumm will include spam subject lines in the output, which can cause mailcow to reject the outgoing summary email.
Step 3: Test the full email pipeline
docker logs --since 168h mailcowdockerized-postfix-mailcow-1 2>&1 | \
pflogsumm | \
{ printf "Subject: Weekly Mail Summary - =SERVER_HOST=\n\n"; cat; } | \
msmtp =ALERT_EMAIL=
Check your inbox — you should receive a complete weekly summary. ![]()
Step 4: Add the weekly cron job
crontab -e
Add this line (runs every Monday at 6:00 AM UTC):
0 6 * * 1 SUBJECT="Weekly Mail Summary - =SERVER_HOST= (week $(date +%V), $(date +%Y): $(date -d '7 days ago' +%b\ %d) – $(date +%b\ %d))"; docker logs --since 168h mailcowdockerized-postfix-mailcow-1 2>&1 | pflogsumm | { printf "Subject: $SUBJECT\n\n"; cat; } | msmtp =ALERT_EMAIL=
This produces a subject line like:
Weekly Mail Summary - mail.example.org (week 21, 2026: May 11 – May 18)
![]()
--since 168hgives exactly 7 days of logs.date +%Vis the ISO week number. Since the cron runs Monday morning,date -d '7 days ago'is always last Monday — so the date range is accurate.
Part 2: Watchtower Container Monitoring
Step 5: Verify your mailcow Docker network name
docker network ls | grep mailcow
Note the full name of the mailcow-network entry — typically mailcowdockerized_mailcow-network.
Step 6: Add Watchtower to docker-compose.override.yml
nano /opt/mailcow-dockerized/docker-compose.override.yml
Add the following (create the file if it doesn’t exist):
services:
watchtower-mailcow-notify:
image: containrrr/watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOCKER_API_VERSION=1.41
- WATCHTOWER_MONITOR_ONLY=true
- WATCHTOWER_SCHEDULE=0 0 7 * * 1
networks:
- mailcow-network
networks:
mailcow-network:
external: true
name: mailcowdockerized_mailcow-network
![]()
WATCHTOWER_MONITOR_ONLY=truemeans Watchtower never pulls or restarts any container — it only checks for available updates and logs what it finds.WATCHTOWER_SCHEDULE=0 0 7 * * 1runs the check every Monday at 7:00 AM UTC.
Step 7: Start Watchtower
cd /opt/mailcow-dockerized
docker compose up -d watchtower-mailcow-notify
docker compose logs watchtower-mailcow-notify
You should see clean output with no errors:
Watchtower 1.7.1
Using no notifications
Scheduling first run: 2026-05-18 07:00:00 +0000 UTC
Step 8: Add the Watchtower results cron job
crontab -e
Add this line (runs every Monday at 7:05 AM, just after Watchtower’s scan):
5 7 * * 1 docker logs --since 10m mailcowdockerized-watchtower-mailcow-notify-1 2>&1 | grep -i "found new\|error\|warn" | { read -r first; [ -z "$first" ] && exit 0; { printf "Subject: Container Updates Available - =SERVER_HOST=\n\n"; printf "$first"; cat; } | msmtp =ALERT_EMAIL=; }
This only sends an email if Watchtower found something noteworthy —
Found newmeans an update is available. If everything is up to date the email is suppressed.--since 10mgrabs just the most recent run’s output.
Part 3: Mailcow Version Check
Step 9: Verify jq is installed
jq --version
If missing:
apt install -y jq
Step 10: Create the version check script
nano /usr/local/bin/mailcow-version-check.sh
Paste the following:
#!/bin/bash
# Checks installed mailcow version against latest GitHub release
# Sends email via msmtp if an update is available
MAILCOW_DIR="/opt/mailcow-dockerized"
ALERT_EMAIL="=ALERT_EMAIL="
SERVER_HOST="=SERVER_HOST="
# Get installed version from mailcow.conf
INSTALLED=$(grep "^MAILCOW_GIT_VERSION=" "$MAILCOW_DIR/mailcow.conf" 2>/dev/null | cut -d= -f2 | tr -d '"')
# Get latest release from GitHub API
LATEST=$(curl -sf "https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest" | jq -r '.tag_name')
# Bail out if either value is empty (network error etc.)
if [ -z "$INSTALLED" ] || [ -z "$LATEST" ]; then
exit 0
fi
# Send email only if versions differ
if [ "$INSTALLED" != "$LATEST" ]; then
printf "Subject: Mailcow Update Available - $SERVER_HOST\n\nInstalled: $INSTALLED\nLatest: $LATEST\n\nRelease notes: https://github.com/mailcow/mailcow-dockerized/releases/tag/$LATEST\n\nTo update, run:\n cd $MAILCOW_DIR && ./update.sh" \
| msmtp "$ALERT_EMAIL"
fi
Make it executable:
chmod +x /usr/local/bin/mailcow-version-check.sh
Step 11: Test the script
Run the script directly to confirm it works:
/usr/local/bin/mailcow-version-check.sh
To test the email pipeline even if you’re already up to date:
INSTALLED="2024-01a" LATEST="2024-02" \
&& printf "Subject: Mailcow Update Available - =SERVER_HOST=\n\nInstalled: $INSTALLED\nLatest: $LATEST\n\nRelease notes: https://github.com/mailcow/mailcow-dockerized/releases/tag/$LATEST\n\nTo update, run:\n cd /opt/mailcow-dockerized && ./update.sh" \
| msmtp =ALERT_EMAIL=
Step 12: Add the cron job
crontab -e
Add this line (runs every Monday at 7:10 AM):
10 7 * * 1 /usr/local/bin/mailcow-version-check.sh
Part 4: Host OS Package Update Notifications
Step 13: Install unattended-upgrades
apt install unattended-upgrades apt-listchanges
Step 14: Configure unattended-upgrades
nano /etc/apt/apt.conf.d/50unattended-upgrades
Find and update these lines (uncomment them if needed):
Unattended-Upgrade::Mail "=ALERT_EMAIL=";
Unattended-Upgrade::MailReport "only-on-error";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
![]()
Automatic-Reboot "false"is critical — you never want a mail server rebooting itself automatically.
Step 15: Enable the unattended-upgrades timer
dpkg-reconfigure -plow unattended-upgrades
Select Yes when prompted. This enables automatic security-only patches. Verify the timer is active:
systemctl status apt-daily.timer apt-daily-upgrade.timer
Both should show active (waiting).
Step 16: Add the package updates cron job
crontab -e
Add this line (runs every day at 8:05 AM):
5 8 * * * apt-get -s upgrade 2>/dev/null | grep "^Inst" | { read -r first; [ -z "$first" ] && exit 0; { printf "Subject: Package Updates Available - =SERVER_HOST=\n\nThe following packages can be updated:\n\n"; printf "$first"; cat; } | msmtp =ALERT_EMAIL=; }
![]()
apt-get -s upgradeis a dry run — the-sflag means “simulate”, nothing is ever installed. The email is only sent if at least one package is waiting.
Step 17: Test the cron command manually
apt-get -s upgrade 2>/dev/null | grep "^Inst" | \
{ printf "Subject: Package Updates Available - =SERVER_HOST=\n\nThe following packages can be updated:\n\n"; cat; } | \
msmtp =ALERT_EMAIL=
The test command sends unconditionally (no early-exit check) so you’ll get an email even if there are no updates, confirming the pipeline works. The cron version only sends if there’s something to report.
Part 5: Applying Updates When Notified
Host OS packages
Take a Hetzner snapshot before applying updates — especially when
systemd, Docker, or kernel packages are involved. Log into console.hetzner.com, select your server, go to Snapshots and click Take Snapshot before proceeding.
apt-get upgrade -y
If a kernel or systemd update was applied, reboot:
reboot
After reconnecting, verify all containers came back up:
cd /opt/mailcow-dockerized && docker compose ps
Clean up orphaned packages periodically:
apt autoremove -y
For held-back packages (shown as kept back):
apt-get dist-upgrade -y
Mailcow version
Take a Hetzner snapshot before running
./update.sh— mailcow updates can include database migrations that are not easily reversible. Always read the release notes at the link in the notification email first.
cd /opt/mailcow-dockerized
./update.sh
Container images (postgres, redis, ghost etc.)
Watchtower reports these but does not update them automatically. To apply:
Take a Hetzner snapshot first for any production service.
cd /opt/mailcow-dockerized
docker compose pull
docker compose up -d
For non-mailcow containers (Ghost, Nextcloud, etc.) follow the update procedure for that service.
Done!
Your complete Monday morning alert stack is running. You will receive:
6:00 AM — Weekly mail activity summary
7:05 AM — Container image update report (only if updates found)
7:10 AM — Mailcow version alert (only if update available)
8:05 AM — Host OS package update report (only if updates found)
Related runbooks: