Skip to content

Instantly share code, notes, and snippets.

@Konfekt
Created October 16, 2025 04:59
Show Gist options
  • Save Konfekt/fad42804eea7013fbe2e2a07a66bdf9d to your computer and use it in GitHub Desktop.
Save Konfekt/fad42804eea7013fbe2e2a07a66bdf9d to your computer and use it in GitHub Desktop.
Automatically add a mutt alias for the sender of each email you open
#!/bin/sh
# Automatically add a mutt alias for the sender of each email you open.
# To use it, add to your muttrc (assuming the script is in your $PATH):
#
# set alias_file=~/.mutt/aliases
# set display_filter = 'auto-add_alias.sh ~/.mutt/aliases'
#
# Originally from https://web.archive.org/web/20130307065724/http://wcm1.web.rice.edu/mutt-tips.html and
# refined at https://github.com/teddywing/mutt-alias-auto-add
MESSAGE="$(cat)"
echo "${MESSAGE}"
alias_file="$1"
if [ -z "$alias_file" ]; then
if command -v mutt >/dev/null 2>&1; then
alias_file="$(mutt -Q "alias_file")"
elif [ -f "$HOME/.muttrc" ]; then
alias_file=$(grep -E --only-matching --no-filename '^\s*set\s+alias_file\s*=.*$' "$HOME/.muttrc")
fi
alias_file=$(echo "${alias_file}" | grep -E --only-matching '[^=]+$' -)
fi
alias_file=$(eval echo "${alias_file}")
alias_file=$(printf "%s" "$alias_file" | sed "s|~|$HOME|")
if ! [ -f "$alias_file" ]; then
echo "No alias file found. Exiting!"
exit 1
fi
# - Unfold and decode the From header (RFC 5322, RFC 2047).
# - Extract email and display name (prefer <...> form; otherwise first email token).
# - Derive alias: from display name (first-last) or sanitized local-part.
# - Normalize display name for storage and quote it.
# - Print one mutt alias line or nothing if no email was found.
NEWALIAS="$(
# Unfold a possibly multiline From header (continues on lines starting with whitespace).
FROM_UNFOLDED=$(
printf "%s" "$MESSAGE" | awk '
BEGIN { found=0 }
/^[Ff]rom:[[:space:]]*/ && !found {
found=1
sub(/^[Ff]rom:[[:space:]]*/, "", $0)
printf "%s", $0
next
}
found && /^[[:space:]]/ {
sub(/^[[:space:]]+/, " ")
printf "%s", $0
next
}
found { exit }
'
)
# Trim surrounding whitespace.
FROM_UNFOLDED=$(printf "%s" "$FROM_UNFOLDED" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
# Decode RFC 2047 encoded-words if possible.
if command -v python3 >/dev/null 2>&1; then
FROM_DECODED=$(python3 - "$FROM_UNFOLDED" <<'PY'
import sys
from email.header import decode_header, make_header
s = sys.argv[1]
try:
print(str(make_header(decode_header(s))), end='')
except Exception:
print(s, end='')
PY
)
elif perl -MEncode::MIME::Header -e 1 >/dev/null 2>&1; then
FROM_DECODED=$(perl -CS -MEncode -e 'binmode STDOUT, ":encoding(UTF-8)"; $_=shift; print decode("MIME-Header", $_)' -- "$FROM_UNFOLDED" 2>/dev/null)
else
FROM_DECODED=$FROM_UNFOLDED
fi
# Extract email address.
EMAIL=$(printf "%s" "$FROM_DECODED" | sed -nE 's/.*<([^>]*)>.*/\1/p')
if [ -z "$EMAIL" ]; then
EMAIL=$(printf "%s" "$FROM_DECODED" | grep -Eo '[[:alnum:]._%+-]+@([[:alnum:]-]+\.)+[[:alpha:]]{2,}' | head -n1)
fi
[ -n "$EMAIL" ] || exit 0
# Extract raw display name and drop RFC 5322 quoted-string wrappers.
NAME=$(
printf "%s" "$FROM_DECODED" \
| sed 's/<[^>]*>.*$//' \
| sed -E '
s/^[[:space:]]+//; s/[[:space:]]+$//; # trim
s/"([^"]*)"/\1/g; # unquote tokens
s/[[:space:]]+/ /g; # collapse spaces
s/^[[:space:]]+//; s/[[:space:]]+$// # trim again
'
)
# Reorder "Last, First Middle" -> "First Middle Last" for alias derivation.
if printf "%s" "$NAME" | grep -Eq '^[^,]+,[[:space:]]*[^,]+$'; then
NAME=$(printf "%s" "$NAME" | sed -E 's/^([^,]+),[[:space:]]*(.+)$/\2 \1/')
fi
# Derive alias from display name when present.
ALIAS=
if [ -n "$NAME" ]; then
NORM="$NAME"
if command -v iconv >/dev/null 2>&1; then
NORM=$(printf "%s" "$NORM" | iconv -f UTF-8 -t ASCII//TRANSLIT 2>/dev/null || printf "%s" "$NORM")
fi
NORM=$(printf "%s" "$NORM" | tr '[:upper:]' '[:lower:]')
TOKENS=$(printf "%s" "$NORM" | sed -E 's/[^[:alnum:]-]+/ /g; s/[[:space:]]+/ /g; s/^[[:space:]]+//; s/[[:space:]]+$//')
if [ -n "$TOKENS" ]; then
FIRST=$(printf "%s\n" "$TOKENS" | awk '{print $1}')
LAST=$(printf "%s\n" "$TOKENS" | awk '{print $NF}')
if [ "$FIRST" != "$LAST" ]; then
ALIAS="${FIRST}-${LAST}"
else
ALIAS="$FIRST"
fi
fi
fi
# Fallback alias from local-part.
if [ -z "$ALIAS" ]; then
LOCALPART=$(printf "%s" "$EMAIL" | sed 's/@.*$//')
ALIAS=$(printf "%s" "$LOCALPART" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^[:alnum:]]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')
fi
# Normalize display name for storage (collapse whitespace) and escape remaining quotes.
NAME_OUT=$(
printf "%s" "$NAME" \
| sed -E 's/[[:space:]]+/ /g; s/^[[:space:]]+//; s/[[:space:]]+$//' \
| sed 's/"/\\"/g'
)
# Print one alias line.
printf 'alias %s "%s" <%s>\n' "$ALIAS" "$NAME_OUT" "$EMAIL"
)"
[ -n "$NEWALIAS" ] && ! grep -Fxq "$NEWALIAS" "$alias_file" && printf '%s\n' "$NEWALIAS" >> "$alias_file"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment