Frequently, I use mutt's alias functionality, to send a message to larger groups of people (legitimate, not spam!). Normally, I put the alias into the Bcc: field, to cancel out spam harvesters, and for privacy reasons (not all recipient know each other).

By accident, I sometimes put the alias into the To: or Cc: field, where it is expanded, revealing all addresses do all recipients. This is very bad, since once sent, the information is leaked, and there's no way back.

  • How can I make mutt refuse to send a mail, if there is more than, say, 1 entry in the To: or 5 entries in the Cc: field?

  • Improvement: How can I make mutt send the mail anyways, if I add a user-defined header, e.g., X-limit-cc: 50?

Setting up a separate mailing list service is not what I'm asking for, this would rather be a separate solution to the problem.


AFAIK, Mutt itself cannot do such checks, but there's a workaround! The idea is to redefine the $sendmail variable to use a wrapper that will check the mail before sending it. For instance, in your .muttrc file:

set sendmail=$HOME/scripts/sendmail-mutt

This script can either terminate with an error (non-zero exit status) or terminate successfully with something like:

exec /usr/sbin/sendmail -oem -oi "$@"

in a shell script, or:

exec qw(/usr/sbin/sendmail -oem -oi), @ARGV;

in Perl.

| improve this answer | |
  • Ok, I have done so. It's ugly, but working fine. Might have some bugs and glitches, though... I'll try to post the script below. – stefan Sep 13 '14 at 17:04
  • Handling these test in the MUA may be was the OP was asking for, unfortunately, it's very easy to subvert. I can talk to sendmail directly. I can telnet to port 25 and talk to the MUA through a pipe. If I have a no telnet to the MTA but I have a compiler, I can easily write a program that talks to port 25. All this points to the MTA as being the proper place for filtering. – msw Sep 13 '14 at 17:51
set -u -e -C;
shopt -s nullglob;

# This script can be used as a `sendmail` replacement in `mutt`.  It
# performs several checks on the mail before sending ist, and it's
# easy to modify these checks, or to add more.  Currently, these are:
#     attachment — fail on non-multipart message if text contains
#         keywords referring to an attachment (English, German).
#     size — refuse to send excessively large mails.
#     fixme — refuse to send mail containing the word `FIXME`.
#     longline — refuse to send mails wit long lines, unless paths
#         (URLs) containing two slashes, or quoted.
#     limit-to, limit-cc — limit number of recipients in the according
#         header.  Better use Bcc field.
# Each of the tests can be disabled by puttig its name in an
# `X-checks` header field.  E.g., `X-checks: longline limit-cc` allows
# to send mails with long lines to unlimited number of recipients in
# the Cc field.
# To use this script, install it where it can be found, and configure
# mutt accordingly.  I have stored this script as `~/.mutt/sendmail`,
# and my `~/.mutt/muttrc` contains the line
#     set sendmail = "~/.mutt/sendmail"
# When sending a mail, it is stored as `mutt-sendmail.*` in a
# temporary location, from where it is accessible for checks.  On
# success, the mail is sent, otherwise this script fails.  The
# temporary file is deleted.

# ---- Configuration of this script ----------------------------------

# Mail is sent using `${sendmail} "$@" <"${wholemail}"`, see last line
# of the script.


# You may explicitly skip some tests by putting their name in a header
# field of your mail.  On checking, these names are printed to stderr.
# The name of the header is:


# ---- The script ----------------------------------------------------

# print message to stderr, maybe fail
function err { echo "$@" >&2; exit 1; }
function warn { echo "$@" >&2; }

# store the mail in a temporary file
wholemail="$(mktemp -t mutt-sendmail.XXXXXXXXXX)" &&
    trap "rm -f '${wholemail}'" EXIT &&
    cat >| "${wholemail}" ||
    err 'cannot create temporary file';

# get the header, with indented lines joined
header="$(sed -rn ':a;/^$/q;$!N;s/\n\s+/ /;ta;P;D' "${wholemail}")";

# get values of one particular header, each occurence on one line
function getHeader { sed -rn "s/^${1}:\s+//p" <<< "$header"; }

# use this function to read body of message
function getBody { sed '1,/^$/d' "${wholemail}"; }

# get list of checks to be ignored for this particular mail
checks="$(getHeader "${myhdr}")";
function requested {
    if grep -q "${1}" <<< "${checks}"; then
        warn "${myhdr}: ${1} — skipping";
        return 1;
        warn "no ${myhdr}: ${1}";
        return 0;

# --- do all tests here ----------------------------------------------
# `$wholemail` contains the file name of the whole mail, as passed to
#   sendmail.
# `getBody` writes the body of the mail to stdout.
# `getHeader foo` writes one line for each value of a `foo:` header.
# `requested bar` fails if a `$myhdr` header contains `bar`.

# make a keyword search on the whole email, if it is not multipart.
requested 'attachment' &&
getHeader "Content-Type" | grep -vq multipart &&
grep -v '^>' "$wholemail" | grep -Eni 'attach|anhang|hängt|unten' &&
err "no multipart message, but hints to attachment.";

# reject emails greater than $maxsize bytes
requested 'size' &&
test "$(stat -c%s "${wholemail}")" -gt "${maxsize}" &&
err "bigger then ${maxsize} bytes.";

# reject emails containing FIXME
requested 'fixme' &&
getBody | grep -n FIXME &&
err "FIXME keyword found.";

# reject emails with long lines
requested 'longline' &&
test "$(getBody | grep -Ev '^>|/.*/' | wc -L)" -gt "$maxline" &&
err "lines longer than $maxline chars found.";

# count number of recipients
requested 'limit-to' &&
test "$(getHeader "To" | grep -o ',' | wc -l)" -gt 2 &&
err 'Too many recipients in the To field.';

requested 'limit-cc' &&
test "$(getHeader "Cc" | grep -o ',' | wc -l)" -gt 5 &&
err 'Too many recipients in the Cc field.';

# --- now send the mail ----------------------------------------------
#err 'debugging mode: not sending';

${sendmail} "$@" <"${wholemail}";
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.