#!/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]... [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]... [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}"