#!/bin/sh # - Syncs mail for all accounts, or a single account given as an argument. # - Displays a notification showing the number of new mails. # - Displays a notification for each new mail with its subject displayed. # - Runs notmuch to index new mail. # - This script can be set up as a cron job for automated mail syncing. # There are many arbitrary and ugly features in this script because it is # inherently difficult to pass environmental variables to cronjobs and other # issues. It also should at least be compatible with Linux (and maybe BSD) with # Xorg and MacOS as well. USAGE="Usage:\n\ $(basename "$0") [-p] [-s ] [-S ] []\n\ Arguments: account: name of account to sync, can be any number of accounts, including zero (which will lead to\n\ all accounts being synced).\n\ Options: -p: Force private mode (by default emails will be displayed with sender and title if fewer than 5).\n\ This option will display only the mailbox and the number of emails, regardless of how many\n\ there are.\n\ -s: Symbol to use for emails (default \`📧\`).\n\ Change to what you want to see or pass empty string for less eye-candy.\n\ -S: Symbol to use for mailboxes (default \`📬\`).\n\ Change to what you want to see or pass empty string for less eye-candy.\n\ -h: displays help message." # Read Options: while getopts ":s:S:ph" flag do case "$flag" in s) mailsymbol="$OPTARG" ;; S) boxsymbol="$OPTARG" ;; p) private_mode="$OPTARG" ;; h) echo -e "$USAGE" exit 0 ;; \?) echo "Invalid option: -$OPTARG" >&2 exit 1 ;; :) echo "Option -$OPTARG requires an argument." >&2 exit 1 ;; esac done # Shift pointer to read accounts: shift $((OPTIND - 1)) # Set default symobols: if [[ -z ${mailsymbol+.} ]]; then mailsymbol="📧" fi if [[ -z ${boxsymbol+.} ]]; then boxsymbol="📬" fi # Run only if not already running in other instance pgrep mbsync >/dev/null && { echo "mbsync is already running."; exit ;} # First, we have to get the right variables for the mbsync file, the pass # archive, notmuch and the GPG home. This is done by searching common profile # files for variable assignments. This is ugly, but there are few options that # will work on the maximum number of machines. eval "$(grep -h -- \ "^\s*\(export \)\?\(MBSYNCRC\|MPOPRC\|PASSWORD_STORE_DIR\|PASSWORD_STORE_GPG_OPTS\|NOTMUCH_CONFIG\|GNUPGHOME\|MAILSYNC_MUTE\|XDG_CONFIG_HOME\|XDG_DATA_HOME\)=" \ "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.zprofile" "$HOME/.config/zsh/.zprofile" "$HOME/.zshenv" \ "$HOME/.config/zsh/.zshenv" "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/zsh/.zshrc" \ "$HOME/.pam_environment" 2>/dev/null)" export GPG_TTY="$(tty)" [ -n "$MBSYNCRC" ] && alias mbsync="mbsync -c $MBSYNCRC" || MBSYNCRC="$HOME/.mbsyncrc" [ -n "$MPOPRC" ] || MPOPRC="$HOME/.config/mpop/config" lastrun="${XDG_CONFIG_HOME:-$HOME/.config}/mutt/.mailsynclastrun" # Settings are different for MacOS (Darwin) systems. case "$(uname)" in Darwin) notify() { osascript -e "display notification \"$2 in $1\" with title \"Account: $account\" subtitle \"$2 new mail(s)\"" && sleep 2 ;} messageinfo() { osascript -e "display notification with title \"$mailsymbol$from [$account]\" subtitle \"$subject\"" ;} ;; *) case "$(readlink -f /sbin/init)" in *systemd*|*openrc*) export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus ;; esac # remember if a display server is running since `ps` doesn't always contain a display pgrepoutput="$(pgrep -ax X\(\|org\|wayland\))" [ -z $displays ] && [ -d /tmp/.X11-unix ] && displays=$(cd /tmp/.X11-unix && for x in X*; do echo ":${x#X}"; done) notify() { [ -n "$pgrepoutput" ] && for x in ${displays:-0:}; do export DISPLAY=$x notify-send --app-name="mutt-wizard" "Account: $1" "$boxsymbol $2 new mail(s)." done ;} messageinfo() { [ -n "$pgrepoutput" ] && for x in ${displays:-0:}; do export DISPLAY=$x notify-send --app-name="mutt-wizard" "$mailsymbol$from [$account]" "$subject" done ;} ;; esac # Check account for new mail. Notify if there is new content. syncandnotify() { acc="$(echo "$account" | sed "s/.*\///")" if [ "$1" = "pop" ]; then # Handle POP mpop "$acc" else # Handle IMAP if [ -z "$opts" ]; then mbsync "$acc"; else mbsync "$opts" "$acc"; fi fi new=$(find\ "$HOME/.local/share/mail/$acc/"[Ii][Nn][Bb][Oo][Xx]/new/ \ "$HOME/.local/share/mail/$acc/"[Ii][Nn][Bb][Oo][Xx]/cur/ \ -type f -newer "$lastrun" 2> /dev/null) newcount=$(echo "$new" | sed '/^\s*$/d' | wc -l) [ -z "$MAILSYNC_MUTE" ] && case 1 in $((newcount > 5)) ) notify "$acc" "$newcount" ;; $((newcount > 0)) ) for file in $new; do # Extract subject and sender from mail. from=$(awk '/^From: / && ++n ==1,/^\<.*\>:/' "$file" | perl -CS -MEncode -ne 'print decode("MIME-Header", $_)' | awk '{ $1=""; if (NF>=3)$NF=""; print $0 }' | sed 's/^[[:blank:]]*[\"'\''\<]*//;s/[\"'\''\>]*[[:blank:]]*$//') subject=$(awk '/^Subject: / && ++n == 1,/^\<.*\>: / && ++i == 2' "$file" | head -n 1 | perl -CS -MEncode -ne 'print decode("MIME-Header", $_)' | sed 's/^Subject: //' | sed 's/^{[[:blank:]]*[\"'\''\<]*//;s/[\"'\''\>]*[[:blank:]]*$//' | tr -d '\n') messageinfo & done ;; esac } # Sync accounts passed as argument or all. if [ "$#" -gt "0" ]; then for arg in "$@"; do [ "${arg%${arg#?}}" = '-' ] && opts="${opts:+${opts} }${arg}" && shift 1 done accounts=$* fi [ -z "$imap_accounts" ] && [ -r "$MBSYNCRC" ] && imap_accounts="$(awk '/^Channel/ {print $2}' "$MBSYNCRC" 2>/dev/null)" [ -z "$pop_accounts" ] && [ -r "$MPOPRC" ] && pop_accounts="$(awk '/^account/ {print $2}' "$MPOPRC" 2>/dev/null)" # Parallelize multiple accounts for account in $imap_accounts; do if [ -n "$accounts" ]; then for tmp_ac in $accounts; do [ "$tmp_ac" = "$account" ] && syncandnotify "imap" & done continue fi syncandnotify "imap" & done for account in $pop_accounts; do if [ -n "$accounts" ]; then for tmp_ac in $accounts; do [ "$tmp_ac" = "$account" ] && syncandnotify "pop" & done continue fi syncandnotify "pop" & done wait notmuch new --quiet #Create a touch file that indicates the time of the last run of mailsync touch "$lastrun"