[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