--- /dev/null
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
--- /dev/null
+PREFIX=/usr
+# Where the script binary should go
+BINPATH = $(PREFIX)/bin
+SBINPATH = $(PREFIX)/sbin
+MANPATH = $(PREFIX)/share/man
+SHAREDIR = $(PREFIX)/share/smailq
+CONFFILE = $(SHAREDIR)/smailq.conf.sample
+
+######################################################################
+
+all: manpage
+
+install: all
+ install -m 0755 smailq $(BINPATH)/smailq
+ install -m 0755 mailq $(BINPATH)/mailq
+ install -m 0755 sendmail $(SBINPATH)/sendmail
+ mkdir -p $(SHAREDIR)
+ install -m 0644 smailq.conf $(CONFFILE)
+ install -m 0644 smailq.1 $(MANPATH)/man1/smailq.1
+ bzip2 -f $(MANPATH)/man1/smailq.1
+
+manpage: smailq.1
+
+smailq.1: smailq.docbook
+ docbook2man.pl $<
+ #groff -t -e -mandoc -Tps smailq.1 > smailq.ps
+
+
--- /dev/null
+#!/bin/sh
+
+# Copyright (c) 2013 Stefan Huber <shuber@sthu.org>
+
+set -e
+
+PROGNAME=$(basename $0)
+
+usage() {
+ cat <<EOF
+List the mails in the mail queue by calling 'smailq --list'.
+
+USAGE:
+ $0 [OPTION]
+
+OPTIONS:
+ -h, --help Print this text
+EOF
+}
+
+
+TEMP=`getopt -o "h" --long "help" -n "$PROGNAME" -- "$@"`
+eval set - "$TEMP"
+
+while true; do
+ case "$1" in
+ -h | --help )
+ usage
+ exit $STATE_OK ;;
+ -- )
+ shift
+ break ;;
+ * )
+ break ;;
+ esac
+done
+
+smailq --list
--- /dev/null
+#!/bin/sh
+
+# Copyright (c) 2013 Stefan Huber <shuber@sthu.org>
+
+set -e
+
+CMD="$(dirname $0)/smailq"
+options=
+list="0"
+
+usage() {
+ cat << EOF
+A wrapper script for smailq that behaves like sendmail.
+
+USAGE:
+ $0 [OPTIONS] [recipient ...]
+ $0 --help
+
+It simply calls '$CMD --send -- [OPTIONS] [recipient ...]'. In
+particular, it passes all options as MSA options to smailq.
+EOF
+}
+
+
+options=()
+while [ $# -gt "0" ]; do
+ if [ "$1" == "-bp" ]; then
+ list="1"
+ else
+ options+=("$1")
+ fi
+ shift
+done
+
+if [ "$list" = "1" ]; then
+ smailq --list
+else
+ smailq --send -- "${options[@]}"
+fi
+
--- /dev/null
+#!/usr/bin/env python3
+"""A mail queue for lightweight SMTP clients (MSAs) like msmtp."""
+
+__author__ = "Stefan Huber"
+__copyright__ = "Copyright 2013"
+
+__license__ = "LGPL-3"
+__version__ = "1.0"
+
+
+from contextlib import contextmanager
+import configparser
+import fcntl
+import getopt
+import os
+import pickle
+import random
+import shlex
+import subprocess
+import sys
+import time
+import socket
+
+
+verbose = False
+quiet = False
+
+
+class Config:
+ """Configuration read from a config file"""
+
+ class ConfigError(RuntimeError):
+ """Error when reading config file"""
+ def __init__(self, value):
+ self.value = value
+ self.message = value
+
+ def __init__(self, conffn):
+ self.logdir = None
+ self.datadir = None
+ self.nwtesthost = None
+ self.nwtestport = None
+ self.nwtesttimeout = None
+ self.msacmd = None
+
+ self.__nwtest = None
+
+ self.__read(conffn)
+
+ def __read(self, conffn):
+ conf = configparser.RawConfigParser()
+ conf.read(conffn)
+
+ self.logdir = "~/.smailq/log"
+ self.datadir = "~/.smailq/data"
+ self.nwtesthost = "www.google.com"
+ self.nwtestport = 80
+ self.nwtesttimeout = 8
+
+ self.logdir = conf.get("general", "logdir", fallback=self.logdir)
+ self.datadir = conf.get("general", "datadir", fallback=self.datadir)
+ self.nwtesthost = conf.get("nwtest", "host", fallback=self.nwtesthost)
+ self.nwtestport = conf.getint("nwtest", "port",
+ fallback=self.nwtestport)
+ self.nwtesttimeout = conf.getint("nwtest", "timeout",
+ fallback=self.nwtesttimeout)
+
+ if not conf.has_option("msa", "cmd"):
+ raise Config.ConfigError("Section 'msa' contains no 'cmd' option.")
+ self.msacmd = conf.get("msa", "cmd")
+
+ def getdatadir(self):
+ """Returns the directory for the mail data"""
+ return os.path.expanduser(self.datadir)
+
+ def getlogdir(self):
+ """Returns the directory for the log data"""
+ return os.path.expanduser(self.logdir)
+
+ def getlockfn(self):
+ """Get a lock filename of the data directory"""
+ return self.getdatadir() + "/.lock"
+
+ def getmailfn(self, id):
+ return self.getdatadir() + "/" + id + ".eml"
+
+ def getmsaargsfn(self, id):
+ return self.getdatadir() + "/" + id + ".msaargs"
+
+ @contextmanager
+ def aquiredatalock(self):
+ """Get a lock on the data directory"""
+ fn = self.getlockfn()
+
+ # If lock file exists, wait until it disappears
+ while os.path.exists(fn):
+ time.sleep(0.05)
+
+ # Use lockf to get exclusive access to file
+ fp = open(fn, 'w')
+ fcntl.lockf(fp, fcntl.LOCK_EX)
+ try:
+ yield
+ finally:
+ fcntl.lockf(fp, fcntl.LOCK_UN)
+ fp.close()
+ os.remove(self.getlockfn())
+
+ def networktest(self):
+ """Test if we have connection to the internet."""
+
+ if self.__nwtest is None:
+ self.__nwtest = False
+ try:
+ host = (self.nwtesthost, self.nwtestport)
+ to = self.nwtesttimeout
+ with socket.create_connection(host, timeout=to):
+ self.__nwtest = True
+ except OSError as e:
+ pass
+ except Exception as e:
+ printerr(e)
+
+ return self.__nwtest
+
+
+class MailQueue:
+
+ def __init__(self, conf):
+ self.conf = conf
+ self.__mailids = None
+
+ def get_mail_ids(self):
+ """Return a list of all mail IDs"""
+
+ # Get mail and msaargs files in datadir
+ listdir = os.listdir(self.conf.getdatadir())
+ mailfiles = [f for f in listdir if f.endswith(".eml")]
+ msaargsfiles = [f for f in listdir if f.endswith(".msaargs")]
+
+ # Strip of file endings
+ mailfiles = [f[:-4] for f in mailfiles]
+ msaargsfiles = [f[:-8] for f in msaargsfiles]
+
+ # Check if symmetric difference is zero
+ for f in set(mailfiles) - set(msaargsfiles):
+ printerr("For ID %s an eml file but no msaargs file exists." % f)
+ for f in set(msaargsfiles) - set(mailfiles):
+ printerr("For ID %s a msaargs file but no eml file exists." % f)
+
+ # Get mail IDs
+ return set(mailfiles) & set(msaargsfiles)
+
+ def getmailinfo(self, id):
+ """Get some properties of mail with given ID"""
+ assert(id in self.get_mail_ids())
+
+ mailfn = self.conf.getmailfn(id)
+
+ info = {}
+ info['ctime'] = time.ctime(os.path.getctime(mailfn))
+ info['size'] = os.path.getsize(mailfn)
+
+ with open(mailfn, "r") as f:
+ mail = f.readlines()
+
+ for l in mail:
+ if l.startswith("Subject:"):
+ info['subject'] = l[8:].strip()
+ break
+
+ for l in mail:
+ if l.startswith("To:"):
+ info['to'] = l[3:].strip()
+ break
+
+ return info
+
+ def printmailinfo(self, id):
+ """Print some info on the mail with given ID"""
+
+ print("ID %s:" % id)
+
+ if not id in self.get_mail_ids():
+ printerr("ID %s is not in the queue!" % id)
+ return
+
+ info = self.getmailinfo(id)
+
+ print(" Time: %s" % info['ctime'])
+ print(" Size: %s Bytes" % info['size'])
+ print(" To: %s" % info['to'])
+ print(" Subject: %s" % info['subject'])
+
+ def listqueue(self):
+ """Print a list of mails in the mail queue"""
+
+ ids = self.get_mail_ids()
+ print("%d mails in the queue.\n" % len(ids))
+ for id in ids:
+ self.printmailinfo(id)
+ print()
+
+ def deletemail(self, id):
+ """Attempt to deliver mail with given ID"""
+ printinfo("Removing mail with ID " + id)
+
+ if not id in self.get_mail_ids():
+ printerr("ID %s is not in the queue!" % id)
+ return
+
+ os.remove(conf.getmailfn(id))
+ os.remove(conf.getmsaargsfn(id))
+ log(conf, "Removed from queue.", id=id)
+
+ def delivermail(self, id):
+ """Attempt to deliver mail with given ID"""
+ printinfo("Deliver mail with ID " + id)
+
+ if not id in self.get_mail_ids():
+ printerr("ID %s is not in the queue!" % id)
+ return
+
+ if not self.conf.networktest():
+ printinfo("Network down. Do not deliver mail.")
+ return
+
+ info = self.getmailinfo(id)
+ log(conf, "Attempting to deliver mail. To=%s" % info['to'], id=id)
+
+ # Read the mail
+ mailfn = self.conf.getmailfn(id)
+ mailf = open(mailfn, "r")
+
+ # Read the options
+ msaargsfn = self.conf.getmsaargsfn(id)
+ msaargs = None
+ with open(msaargsfn, "rb") as f:
+ msaargs = pickle.load(f)
+
+ # Build argv for the MSA
+ msacmd = self.conf.msacmd
+ msaargv = shlex.split(msacmd)
+ msaargv += msaargs
+
+ # Call the MSA and give it the mail
+ printinfo("Calling " + " ".join([shlex.quote(m) for m in msaargv]))
+ ret = subprocess.call(msaargv, stdin=mailf)
+
+ if ret == 0:
+ log(conf, "Delivery successful.", id=id)
+ self.deletemail(id)
+ else:
+ log(conf, "Delivery failed with exit code %d." % ret, id=id)
+
+ def delivermails(self):
+ """Attempt to deliver all mails in the mail queue"""
+ printinfo("Deliver mails in the queue.")
+
+ if not self.conf.networktest():
+ printinfo("Network down. Do not deliver mails.")
+ return
+
+ for id in self.get_mail_ids():
+ self.delivermail(id)
+
+ def enqueuemail(self, mail, msaargs):
+ """Insert the given mail into the mail queue"""
+ # Creeate a new ID
+ id = None
+ while True:
+ nibbles = 8
+ id = hex(random.getrandbits(4*nibbles))[2:].upper()
+ while len(id) < nibbles:
+ id = '0' + id
+ if not os.path.exists(self.conf.getmailfn(id)):
+ break
+
+ log(conf, "Insert into queue.", id=id)
+
+ # Write the mail
+ mailfn = self.conf.getmailfn(id)
+ with open(mailfn, "w") as f:
+ f.write(mail)
+
+ # Write the options
+ msaargsfn = self.conf.getmsaargsfn(id)
+ with open(msaargsfn, "wb") as f:
+ pickle.dump(msaargs, f)
+
+ return id
+
+ def sendmail(self, mail, msaargs):
+ """Insert a mail in the mail queue, and attempt to deliver mails"""
+ self.enqueuemail(mail, msaargs)
+ self.delivermails()
+
+
+def log(conf, msg, id=None):
+ """Write message to log file"""
+ fn = conf.getlogdir() + "/smailq.log"
+
+ with open(fn, 'a') as f:
+ fcntl.lockf(f, fcntl.LOCK_EX)
+ # Prepend ID to msg
+ if id is not None:
+ msg = ("ID %s: " % id) + msg
+ # Prepend time to msg
+ msg = time.strftime("%Y-%m-%d %H:%M:%S: ", time.localtime()) + msg
+
+ # Write msg line
+ f.write(msg + "\n")
+ if not quiet:
+ print(msg)
+
+ fcntl.lockf(f, fcntl.LOCK_UN)
+
+
+def printerr(msg):
+ """Print an error message"""
+ print(msg, file=sys.stderr)
+
+
+def printinfo(msg):
+ if verbose:
+ print(msg)
+
+
+def version():
+ """Show version info"""
+
+ print("smailq " + __version__)
+ print("Copyright (C) 2013 Stefan Huber")
+
+
+def usage():
+ """Print usage text of this program"""
+
+ print("""
+smailq is a mail queue for lightweight SMTP clients (MSAs) like msmtp that do
+not provide a queue. It basically provides the functionality of sendmail and
+mailq.
+
+USAGE:
+
+ {0} --send [recipient ...] -- [MSA options ...]
+ {0} --list
+ {0} --deliver-all
+ {0} --deliver [ID ...]
+ {0} --delete [ID ...]
+ {0} --help
+ {0} --version
+
+COMMANDS:
+
+ --delete
+ Remove the mails with given IDs from the queue.
+
+ --deliver
+ Attempt to deliver the mails with given IDs only.
+
+ --deliver-all
+ Attempt to deliver all mails in the queue.
+
+ -h, --help
+ Print this usage text.
+
+ --list
+ List all mails in the queue. This is the default
+
+ --send
+ Read a mail from stdin, insert it into the queue, and attempt to
+ deliver all mails in the queue. Options after "--" are passed forward
+ to the MSA for this particular mail.
+
+ -V, --version
+ Show version info.
+
+OPTIONS:
+
+ -C, --config=FILE
+ Use the given configuration file instead of "$HOME/.smailq.conf".
+
+ -q, --quiet
+ Do not print info messages.
+
+ -v, --verbose
+ Increase output verbosity.
+""".format(sys.argv[0]))
+
+
+if __name__ == "__main__":
+
+ conffn = os.path.expanduser("~/.smailq.conf")
+ cmd = "--list"
+ nooptargs = []
+
+ try:
+
+ longopts = ["config=", "delete", "deliver-all", "deliver", "help",
+ "list", "send", "verbose", "version", "quiet"]
+ opts, nooptargs = getopt.gnu_getopt(sys.argv[1:], "hC:vVq", longopts)
+
+ for opt, arg in opts:
+ if opt in ['-h', '--help']:
+ usage()
+ sys.exit(os.EX_OK)
+ elif opt in ['-V', '--version']:
+ version()
+ sys.exit(os.EX_OK)
+ elif opt in ['--list', '--send', '--delete', '--deliver-all',
+ '--deliver']:
+ cmd = opt
+ elif opt in ['-C', '--config']:
+ conffn = arg
+ elif opt in ['-v', '--verbose']:
+ verbose = True
+ quiet = False
+ elif opt in ['-q', '--quiet']:
+ quiet = True
+ verbose = False
+ else:
+ assert(False)
+
+ except getopt.GetoptError as e:
+ printerr("Error parsing arguments:", e)
+ usage()
+ sys.exit(os.EX_USAGE)
+
+ # Reading config file
+ if not os.path.isfile(conffn):
+ printerr("No such config file:", conffn)
+ sys.exit(os.EX_IOERR)
+ conf = None
+ try:
+ conf = Config(conffn)
+
+ if not os.path.isdir(conf.getdatadir()):
+ printerr("Data directory does not exist: " + conf.getdatadir())
+ sys.exit(os.EX_IOERR)
+
+ if not os.path.isdir(conf.getlogdir()):
+ printerr("Log directory does not exist: " + conf.getlogdir())
+ sys.exit(os.EX_IOERR)
+
+ except Exception as e:
+ printerr("Error reading config file:", e)
+ sys.exit(os.EX_IOERR)
+
+ try:
+ with conf.aquiredatalock():
+
+ printinfo("Aquired the lock.")
+
+ mq = MailQueue(conf)
+ if cmd == "--send":
+ mail = sys.stdin.read()
+ mq.sendmail(mail, nooptargs)
+ elif cmd == "--list":
+ mq.listqueue()
+ elif cmd == "--deliver-all":
+ mq.delivermails()
+ elif cmd == "--deliver":
+ for id in nooptargs:
+ mq.delivermail(id)
+ elif cmd == "--delete":
+ for id in nooptargs:
+ mq.deletemail(id)
+
+ except OSError as e:
+ printerr(e)
+ sys.exit(os.EX_IOERR)
--- /dev/null
+# General settings concerning smailq
+[general]
+# Optional: The directory where the log file is written to
+logdir = ~/.smailq/log/
+# Optional: The directory where smailq saves the mail queue data
+datadir = ~/.smailq/data/
+
+# Settings for the network (TCP) connectivity test
+[nwtest]
+# Optional: The host to connect to
+host = www.google.com
+# Optional: The port to connect to
+port = 80
+# Optional: The timeout
+timeout = 8
+
+# Settings concerning the mail submission agent
+[msa]
+# This command is called when smailq attempts to deliver a mail. The
+# MSA-options passed to smailq are appended to this line.
+cmd = /usr/bin/msmtp
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+
+ <refentryinfo>
+ <address>
+ <email>shuber@sthu.org</email>
+ </address>
+ <author>
+ <firstname>Stefan</firstname>
+ <surname>Huber</surname>
+ </author>
+ <date>2013-12-29</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>smailq</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo>smailq 1.0</refmiscinfo>q
+ </refmeta>
+
+ <refnamediv>
+ <refname><application>smailq</application></refname>
+ <refpurpose>A simple mail queue.</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>smailq</command>
+ <option>--send</option>
+ <arg rep="repeat"><replaceable>recipient</replaceable></arg>
+ <option>--</option>
+ <arg rep="repeat"><replaceable>MSA options</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>smailq</command>
+ <option>--list</option>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>smailq</command>
+ <option>--deliver-all</option>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>smailq</command>
+ <option>--deliver</option>
+ <arg rep="repeat"><replaceable>ID</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>smailq</command>
+ <option>--delete</option>
+ <arg rep="repeat"><replaceable>ID</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>smailq</command>
+ <option>--help</option>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>smailq</command>
+ <option>--version</option>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>
+ <command>smailq</command> is a mail queue for lightweight SMTP
+ clients (MSAs) like msmtp that do not provide a queue. It basically
+ provides the functionality of sendmail and mailq.
+ </para>
+
+ <para>
+ When <command>smailq</command> sends a mail it first inserts the
+ mail into the message queue and then attempts to deliver all mails
+ in the queue using an external MSA such as msmtp.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>COMMANDS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--delete</option></term>
+ <listitem>
+ <para>Remove the mails with given IDs from the queue.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--deliver</option></term>
+ <listitem>
+ <para>Attempt to deliver the mails with given IDs only.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--deliver-all</option></term>
+ <listitem>
+ <para>Attempt to deliver all mails in the queue.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print usage text.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list</option></term>
+ <listitem>
+ <para>List all mails in the queue. This is the default.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--send</option></term>
+ <listitem>
+ <para>Read a mail from stdin, insert it into the queue, and
+ attempt to deliver all mails in the queue. Options after
+ "--" are passed forward to the MSA for this particular
+ mail.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-V</option></term>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Show version info.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-C</option></term>
+ <term><option>--config FILE</option></term>
+ <listitem>
+ <para>Use the given configuration file instead of
+ "$HOME/.smailq.conf".</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--quiet</option></term>
+ <listitem>
+ <para>Do not print info messages.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-v</option></term>
+ <term><option>--verbose</option></term>
+ <listitem>
+ <para>Increase output verbosity.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>CONFIGURATION FILES</title>
+
+ <para>
+ By default, <command>smailq</command> reads $HOME/.smailq.conf as
+ configuration file. The syntax follows RFC 822. A sample
+ configuration file contains the following lines:
+ </para>
+
+ <programlisting>
+[general]
+# Optional: The directory where the log file is written to
+logdir = ~/.smailq/log/
+# Optional: The directory where smailq saves the mail queue data
+datadir = ~/.smailq/data/
+
+# Settings for the network (TCP) connectivity test
+[nwtest]
+# Optional: The host to connect to
+host = www.google.com
+# Optional: The port to connect to
+port = 80
+# Optional: The timeout
+timeout = 8
+
+# Settings concerning the mail submission agent
+[msa]
+# This command is called when smailq attempts to deliver a mail. The
+# MSA-options passed to smailq are appended to this line.
+cmd = /usr/bin/msmtp
+ </programlisting>
+
+ </refsect1>
+
+ <refsect1>
+ <title>BUGS</title>
+ Bug reports to Stefan Huber <email>shuber@sthu.org</email>.
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>
+ <author>
+ <firstname>Stefan</firstname>
+ <surname>Huber</surname>
+ <contrib>Original author</contrib>
+ <email>shuber@sthu.org</email>
+ </author>
+ </para>
+ </refsect1>
+</refentry>