[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[msmtp-users] New script for queue management



Hi all,

Chris Gianniotis wrote a shell script that manages a mail queue for
msmtp. It combines and extends the functionality of the msmtpqueue
scripts. I'd like to include it into the scripts subdirectory of the
next release. Please let us know what you think about it.

Martin
I've modified the scripts comprising Martin Lambers' msmtpqueue package
in an attempt to combine all queue functions into one routine called
msmptq. Please let me know how this works and whether it is useful and
how it might be improved. While the queue management functions are all
invoked by options there are cases where the msmtpq opts need to be
distinct from msmtp's own opts. So (as with msmtp) I have separated the
two sets of opts with a '--', where necessary, but it is also useful to
have an invocation for simply sending/queuing a mail (msmtp calls it
'sendmail' mode). This is useful for a mail client, e.g. mutt's "set
sendmail = ...". I've set up a symlink, called msmtpQ, for this ; if
msmtpq is invoked via the msmtpQ link it simply passes the command line
(and piped mail) through to msmtp - keeping things simple and snappy.

As usual, credit goes to Martin Lambers for the idea ; any bugs, design
errors, oversights, etc. belong to me.


Therefore, for sending/enqueuing mail :

-- msmtpq -s -- (the 'send' op) ; (synonym : msmtpQ) -- sending a mail
   (from, e.g., mutt) will cause the passed mail to be sent directly out
   to the remote server if the host is connected ; if the host is not
   connected or if msmtp returns an error the mail is queued

-- msmtpq -e -- (the 'enqueue' op) -- a mail is explicitly queued,
   whether or not the host is connected

-- msmtpq -sr -- (the 'send & run' option) -- if mail has accumulated in
   the queue, a mail can be sent and the queue then automatically run

-- msmtpq -er -- (the 'enqueue' & 'run' option) -- one can also queue a
   mail, then run the queue ; not certain why anyone would do this or if
   it's useful at all ...

For queue management :

-- msmtpq -r -- runs the contents of the queue

-- msmtpq -d -- displays the contents of the queue

-- msmtpq -m -- allows the purging of one or more mails

-- msmtpq -a -- purges all mail in the queue

Please note that :

-- only one op, one of the above, can be executed ; if more than one op
   is entered only the first is executed ; only the above combined ops
   are accepted

-- if the ops -s, -e, -sr or -er are used, they *must* be followed by a
   '--' to separate the msmtpq ops from the msmtp ops (msmtpQ requires
   no op as it is essentially msmtpq -s and simply passes everything
   through to msmtp), if it can ; the above list of ops pass a mail
   through to msmtp or to the queue


Installation :

Copy msmtpq to wherever is best for you (I use /usr/local/bin) then
create a symbolic link named msmtpQ to it in the same directory
  #ln -s msmtpq msmtpQ

Replace the msmtp invocation in your mail client with msmtpQ ; e.g. for
mutt : set sendmail = /path/to/msmtpQ

That's it.


msmtpq issues & considerations :

-- for testing whether a connection is present / alive I use a ping to
   (the ip address of) www.google.com ; is there a better, faster,
   easier way ? Is it better to use a host within one's ISP's local net
   as a target to test a connection ; e.g., a local dns server ? ; in
   that case, substitute its IP address for 'www.google.com' (/bin/ping
   can show which is faster) ; perhaps it's not such a big deal ...

-- I've changed msmtpqueue's filesystem 'view' from changing to the
   queue directory to perform all its functions to that of performing
   the queue functions from wherever it is invoked. This has simplified
   (and likely speeded up) most of the functions by eliminating the use
   of 'pushd' at the beginning and the necessity of keeping track of the
   need to 'popd' at the end or on errors.
#!/bin/sh

#--------------------------------------------------------------
#
#  msmtpq : queue funtions to use and manage the msmtp queue,
#    as it was defined by Martin Lambers
#  Copyright (C) 2008 Chris Gianniotis
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or (at
#  your option) any later version.
#
#--------------------------------------------------------------

#--------------------------------------------------------------
# the msmtp queue contains unique filenames of the following form :
#   two for each mail in the queue
#
# create new unique filenames of the form :
#   MLF: ccyy-mm-dd-hh.mm.ss[-x].mail   -- mail file
#   MSF: ccyy-mm-dd-hh.mm.ss[-x].msmtp  -- msmtp command line file
# where x is a consecutive number only appended for uniqueness
#   if you send more than one mail per second
#--------------------------------------------------------------

#Q=$HOME/.msmtp.queue                 # the queue
Q=~/.msmtp.queue                     # the queue
umask 077                            # set secure permissions on created directories and files
declare -i CNT                       # a count of mail(s) in the queue

usage() {
  echo
  echo '  usage : msmtpq functions'
  echo
  echo '          msmtpq <op> --'
  echo '          ops : -s   send a mail'
  echo '                -sr  send a mail, then run queue'
  echo '                -e   enqueue a mail'
  echo '                -er  enqueue a mail, then run queue'
  echo
  echo '                -r   run (flush) mail queue'
  echo '                -d   display (list) queue'
  echo '                -m   purge a single mail from queue'
  echo '                -a   purge all mail in queue'
  echo '                -h   this helpful blurt'
  echo
  echo '  -s   an outgoing mail will normally be sent directly out,'
  echo '         if connected, otherwise it is queued'
  echo '  -e   causes the mail to be enqueued'
  echo '  -r   flushes the mail queue, if anything is there'
  echo '  -sr  if connected sends out the mail then flushes the queue'
  echo '         otherwise enqueues it'
  echo '  -er  enqueues the mail then flushes the queue'
  echo '         (but why would anyone want to do this ?)'
  echo
  echo '       - note that only one op per invocation is allowed'
  echo '       - the only combined ops are the ones listed above'
  echo '       - if more than one op is specified, the first one'
  echo '           only is executed'
  echo '       - for -s, -e, -sr or -er the msmtpq op MUST be followed'
  echo "           by '--' to separate it from any msmtp options"
  echo
}

# verify that msmtp queue is present
  # the first version can be used if you'd like to create the queue dir
  # if it's not found ; I'd rather just be warned if it's not there
check_queue() {                      # make certain queue dir is present
  #if [ ! -d "$Q" ] ; then            # queue dir present or
	#  /bin/mkdir -p "$Q" || dsp -e 'could not create queue dir'
  #fi                                 # create it
  if [ ! -d "$Q" ] ; then            # queue dir not present
    dsp -e "  can't find $Q"         # complain
  fi
}

# display a message, possibly an error
dsp() {
  local M ERR

  if [ "$1" == '-e' ] ; then
    ERR='t'
    shift
  fi

  for M ; do
    if [ -n "$M" ] ; then
      echo "  $M"
    else
      echo
    fi
  done

  [ -n "$ERR" ] && exit 1
}

# send a mail (otherwise queue it)
send_mail() {    # <-- all mail args # send or enqueue a mail
  local -i RC                        # msmtp return code

  if /bin/ping -qnc 1 -w 2 64.233.183.147 &> /dev/null ; then  # ip addr of www.google.com
    /bin/cat | /usr/bin/msmtp "$@" > /dev/null &   # send mail immediately
    RC=$?
    if $RC ; then                    # send was successful
      dsp 'mail sent'
    else
      dsp 'mail send was unsuccessful' \
      dsp "msmtp rc = $RC ; enqueuing mail"
      enqueue_mail "$@"
    fi
  else                               # not connected
    enqueue_mail "$@"                # enqueue mail
  fi
}

# enqueue a mail
enqueue_mail() { # <-- all mail args # enqueue a mail
  local BASE                         # base name
  local -i INC                       # increment counter for basename collision

  BASE="$Q/`/bin/date +%Y-%m-%d-%H.%M.%S`"  # make unique base filename
  if [ -f "$BASE.*" ] ; then         # base filename exists
	  INC=1                            # initial increment to 1
	  while [ -f "${BASE}-${INC}.*" ] ; do
      INC="(( ++INC ))"              # filename exists ; bump increment
	  done
	  BASE="${BASE}-${INC}"            # unique ; set base filename
  fi

  echo "$@" > "${BASE}.msmtp" || dsp -e 'writing msmtp cmd line' \
           "to ${BASE}.msmtp failed" # write command line to .msmtp file
  /bin/cat  > "${BASE}.mail" || dsp -e 'writing mail' \
           "to ${BASE}.mail failed"  # write mail to .mail file
  dsp 'mail queued'
}

# run (flush) queue
run_queue() {                        # run queue
  local LOC="$Q/.lock" MLF MSF       # lock file name ; queued mail filename pairs
  local -i MAX=120 SEC=0 RC          # max seconds to gain a lock ; seconds waiting

  if (( $(/bin/ls -1 *.mail 2> /dev/null | /usr/bin/wc -l) > 0 )) ; then # if any mail in Q
                                                     # attempt to lock queue
    while [ -e "$LOC" ] && (( SEC < MAX )) ; do      # if a lock file there
	    /bin/sleep 1                                   # wait a second
	    (( ++SEC ))                                    # accumulate seconds
    done                                             # try again while locked for MAX secs
    if [ -e "$LOC" ] ; then                          # lock file still there, give up
	    dsp -e '' "cannot use $Q : waited $MAX seconds for" \
	              "  lockfile $LOC to vanish, giving up" \
	              'if you are sure that no other instance of this script' \
	              '  is running, then delete the lock file' ''
    fi

    /bin/touch "$LOC" || exit 1      # lock queue

    for MLF in *.mail ; do           # process all mails
	    dsp "sending $MLF ..."         # scratch user's itch
	    MSF="${MLF%.*}.msmtp"
	    if [ ! -f "$MSF" ] ; then      # no corresponding MSF file found
		    dsp "corresponding file $MSF not found" \
		        'check this ...'         # give user the bad news
		    continue                     # crank on
	    fi
	    /usr/bin/msmtp $(/bin/cat "$MSF") < "$MLF"  # the mail goes out the door
      RC=$?
	    if $RC ; then                  # send was successful
		    /bin/rm "$MLF" "$MSF"        # nuke the files
		    dsp "$MLF sent successfully" # good news to user
	    else                           # send unsuccessful
		    dsp "$MLF send failed" \
            "msmtp rc = $RC" \
            'check this out ...'     # bad news ...
	    fi
    done
  else                               # no mails in queue
    dsp '' 'mail queue is empty' ''  # inform user
  fi

  /bin/rm -f "$LOC"                  # remove the lock
}

# display queue
display_queue() {
  local LST M                        # list of mails in queue ; each mail

  echo
  LST="$(/bin/ls $Q/*.mail 2>/dev/null)"
  (( CNT = 0 ))
  if [ -n "$LST" ] ; then
    for M in $LST ; do
      echo "  mail id = [$(/usr/bin/basename $M .mail)]"       # show mail id
      /bin/egrep -s --colour -h '(^From:|^To:|^Subject:)' "$M" # show mail info
      (( CNT++ ))
      echo
    done
  else
    dsp 'no mail in queue' ''
  fi
}

# delete all mail in queue
purge_all_mail() { # <-- question txt, positive ack txt
  local YN I C                       # confirmation response ; question text ; ack text

  if [ "$1" == '-1' ] ; then         # queue contains single mail
    I="remove the only mail from the queue"
    C="single mail purged from queue"
  else                               # queue contains multiple mails
    I="remove (purge) all mail from the queue"
    C="msmtp queue purged"
    display_queue
  fi

  echo -n "  $I [y/N] ? ...: " ; read YN
  case $YN in
    y|Y) /bin/rm -f "$Q"/*.*         # msmtp - nukes all mail in queue (dir)
         dsp '' "$C ..." ''
         ;;
    *)   dsp '' 'nothing done ; queue is untouched ...' ''
         ;;
  esac
}

purge_a_mail() {
  local ID                           # id of mail being deleted

  while true ; do                    # purge an individual mail from queue
    display_queue                    # show queue contents, if any
    if (( CNT > 0 )) ; then          # something there
      if (( CNT == 1 )) ; then       # only one mail
        purge_all_mail -1            # purge it
      else                           # more than one mail
        echo '  remove (purge) a mail from the queue ; enter its id'
        echo -n '    ( <cr> to exit ) ...: ' ; read ID
        if [ -n "$ID" ] ; then       # <-- file name (only, no suff)
          if [ -n "$(/bin/ls "$Q"/"$ID".* 2>/dev/null)" ] ; then
            /bin/rm "$Q"/"$ID".*     # msmtp - nukes a single mail in queue (dir)
            dsp '' "mail [$ID] purged ..."
          else                       # invalid id entered
            dsp '' "mail [$ID] not found ; bad id ..."
          fi
        else                         # nothing entered
          dsp '' 'nothing done ; queue is untouched ...' ''
          break
        fi
      fi
    else
      break
    fi
  done
}

#
## -- entry point
#

# check what we're being called as - 'msmtpq' or 'msmtpQ'
if [ "$(/usr/bin/basename $0)" == 'msmtpQ' ] ; then  # called as msmtpQ
  if [ -n "$1" ] ; then              # if arg(s)
    check_queue                      # verify that queue directory is present ...
    send_mail "$@"                   # send mail (or queue it if not connected)
    exit 0
  else
    dsp -e 'msmtpQ requires input of a mail'
  fi
fi

if [ -z "$1" ] ; then
  dsp 'msmtpq requires an instruction &/or a mail'
  usage
  exit 1
fi

for A ; do                           # sort opts, args, etc.
  case "$A" in
    --) shift                        # end of msmtp opts - shift off '--'
        break                        # anything further belongs to msmtp
        ;;
    -h) usage                        # show help
        exit 0
        ;;
    -s|-sr|-e|-er|-r|-d|-m|-a)       # all other valid ops
        [ -z "$OP" ] && OP=${A:1}    # op not already spec'd ; take all but first char (hyphen)
        shift
        ;;
    *)  dsp '' "[$A] is an unknown option"
        usage
        exit 1
        ;;
  esac
done

if [ -z "$OP" ] ; then               # check for an empty op
  dsp -e 'must specify an operation for msmtpq'
fi

check_queue                          # check that queue directory is present ...
while [ -n "$OP" ] ; do              # while op contains a spec
  case "$OP" in                      # sort ops ; run according to spec
    s)  send_mail "@"     ;;         # send the mail
    e)  enqueue_mail "$@" ;;         # enqueue the mail
    r)  run_queue         ;;         # run (flush) the queue
    d)  display_queue     ;;         # display (list) all mail in queue
    m)  purge_a_mail      ;;         # purge an individual mail from queue
    a)  purge_all_mail    ;;         # purge all mail in queue
  esac
  OP=${OP:1}                         # trim off first char of OP
done

exit 0