#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# email — send emails with optional attachments via msmtp
# usage: email [-a account] [-s subject] [-f file]... <recipient> [body]
# examples:
# email kris@example.com "hello"
# email -s "Report" -f data.csv kris@example.com
# echo "body" | email -s "Subject" -f a.csv -f b.csv kris@example.com
die() { printf '%s: %s\n' "${0##*/}" "$*" >&2; exit 1; }
account="gmail"
subject="(no subject)"
files=()
boundary="----=_boundary_$(date +%s)_$$"
while [[ $# -gt 0 ]]; do
case "${1}" in
-a) account="${2}"; shift ;;
-s) subject="${2}"; shift ;;
-f) files+=("${2}"); shift ;;
-h) printf 'usage: email [-a account] [-s subject] [-f file]... <recipient> [body]\n'; exit 0 ;;
-*) die "unknown option: ${1}" ;;
*) break ;;
esac
shift
done
[[ $# -lt 1 ]] && die "no recipient specified"
to="${1}"
shift
# body from arg, stdin, or empty
if [[ $# -gt 0 ]]; then
body="$*"
elif [[ ! -t 0 ]]; then
body="$(cat)"
else
body=""
fi
# build and send
{
printf 'To: %s\n' "${to}"
printf 'Subject: %s\n' "${subject}"
printf 'MIME-Version: 1.0\n'
if [[ ${#files[@]} -eq 0 ]]; then
printf 'Content-Type: text/plain; charset=utf-8\n\n'
printf '%s\n' "${body}"
else
printf 'Content-Type: multipart/mixed; boundary="%s"\n\n' "${boundary}"
# body part
printf -- '--%s\n' "${boundary}"
printf 'Content-Type: text/plain; charset=utf-8\n\n'
printf '%s\n\n' "${body}"
# attachments
for f in "${files[@]}"; do
[[ -f "${f}" ]] || die "file not found: ${f}"
fname="${f##*/}"
printf -- '--%s\n' "${boundary}"
printf 'Content-Type: application/octet-stream; name="%s"\n' "${fname}"
printf 'Content-Disposition: attachment; filename="%s"\n' "${fname}"
printf 'Content-Transfer-Encoding: base64\n\n'
base64 "${f}"
printf '\n'
done
printf -- '--%s--\n' "${boundary}"
fi
} | msmtp -a "${account}" "${to}"
printf 'sent to %s via %s\n' "${to}" "${account}"