Skip to content

Commit

Permalink
document new capabilities of mq-sendmail in README.md, make queue…
Browse files Browse the repository at this point in the history
… dir parameter optional for `mq-run`
  • Loading branch information
FelixSchwarz committed Aug 5, 2024
1 parent 930819e commit d949935
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 28 deletions.
81 changes: 58 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,68 @@

## mailqueue-runner

This library helps sending email messages to an SMTP server. Its main feature
is a queuing system to handle (temporary) errors when sending the message
This library provides a robust way to send email messages to an external SMTP
server. The API was designed for easy integration into your Python (web) application.
Additionally, there is a CLI script which is compatible with `/usr/bin/sendmail`
(with a very limited feature set) so this software is also an alternative to
[msmtp](https://github.com/marlam/msmtp) and [ssmtp](https://packages.qa.debian.org/s/ssmtp.html).

Its main feature is a queuing system to handle (temporary) errors when sending the message
(e.g. interrupted network connection) and detailed error logging.

When a message can not be sent via SMTP it can be stored in a maildir-like queue
When a message cannot be sent via SMTP it can be stored in a maildir-like queue
on disk. An external helper script (`mq-run`) picks them up at a later time and
tries to deliver these messages again. The helper script must be called
regularly (e.g. via cron).

As a nice bonus the library is pretty modular so you can plug in custom code and
As a nice bonus, the library is pretty modular so you can plug in custom code and
adapt the library to your needs.

### Usage (mail submission)

### Usage `mq-sendmail` (CLI)

The code provides a CLI application named `mq-sendmail` which provides (basic)
compatibility with the common Un*x `/usr/bin/sendmail` application. Additionally,
it supports some convenient parameters added by [msmtp](https://github.com/marlam/msmtp).

$ mq-sendmail --set-date-header --set-msgid-header root <<<MAIL
Subject: Test email
From: [email protected]
Mime-Version: 1.0
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8

mail body
MAIL

By default, the configuration read from `~/.mailqueue-runner.conf` or
`/etc/mailqueue-runner.conf` though you can also specify the config file
explicitly using `--config=...`. Similar to other `sendmail` implementations,
the application parses `/etc/aliases` to look up the recipient's email address.

Please note that the code will only enqueue the message after a failed delivery
if the configuration file contains the `queue_dir` option.


### Configuration (CLI scripts)

The configuration file uses the traditional "ini"-like format:

[mqrunner]
smtp_hostname = hostname
smtp_port = 587
smtp_username = [email protected]
smtp_password = secret
# optional but the CLI scripts will not queue messages if this is not set
queue_dir = /path/to/mailqueue
# optional, format as described in
# https://docs.python.org/3/library/logging.config.html#logging-config-fileformat
logging_conf = /path/to/logging.conf

For more information about wrapping `mq-run` (e.g. to reuse an existing configuration format) please read [Cookbook: Custom wrapper for mq-run](#cookbook-custom-wrapper-for-mq-run).


### Usage (mail submission/Python integration)

```python
from schwarz.mailqueue import init_smtp_mailer, MaildirBackend, MessageHandler
Expand All @@ -37,29 +86,15 @@ was_queued = (getattr(send_result, 'queued', None) is not False)

### Usage (mq-run)

The `mq-run` script sends all queued messages to an SMTP server:
If you use queueing to handle temporary delivery problems, you need to run
a script periodically to retry delivery. `mq-run` provides that ability:

mq-run --config=/path/to/config.ini /path/to/queue
$ mq-run

If you want to test your configuration you can send a test message to ensure
the mail flow is set up correctly:

mq-send-test --config=/path/to/config.ini /path/to/queue [email protected]

### Configuration (mq-run)

The configuration file uses the traditional "ini"-like format:

[mqrunner]
smtp_hostname = hostname
smtp_port = 587
smtp_username = [email protected]
smtp_password = secret
# optional, format as described in
# https://docs.python.org/3/library/logging.config.html#logging-config-fileformat
logging_conf = /path/to/logging.conf

For more information about wrapping `mq-run` (e.g. to reuse an existing configuration format) please read [Cookbook: Custom wrapper for mq-run](#cookbook-custom-wrapper-for-mq-run).
$ mq-send-test [email protected]


### Logging
Expand Down
14 changes: 11 additions & 3 deletions schwarz/mailqueue/cli/one_shot_queue_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import docopt

from ..app_helpers import guess_config_path
from ..app_helpers import guess_config_path, parse_config
from ..queue_runner import one_shot_queue_run


Expand All @@ -17,7 +17,7 @@ def one_shot_queue_run_main(argv=sys.argv, return_rc_code=False):
"""mq-run.
Usage:
mq-run [options] <queue_dir>
mq-run [options] [<queue_dir>]
Options:
-C, --config=<CFG> Path to the config file
Expand All @@ -26,9 +26,17 @@ def one_shot_queue_run_main(argv=sys.argv, return_rc_code=False):
arguments = docopt.docopt(one_shot_queue_run_main.__doc__, argv=argv[1:])
config_path = guess_config_path(arguments['--config'])
queue_dir = arguments['<queue_dir>']

if not queue_dir:
settings = parse_config(config_path, section_name='mqrunner')
queue_dir = settings.get('queue_dir')
if not queue_dir:
sys.stderr.write('No queue directory specified\n')
return 10 if return_rc_code else sys.exit(10)

cli_options = {
'verbose': arguments['--verbose'],
}
one_shot_queue_run(queue_dir, config_path, options=cli_options)
exit_code = 0
return exit_code if (return_rc_code) else sys.exit(exit_code)
return exit_code if return_rc_code else sys.exit(exit_code)
3 changes: 3 additions & 0 deletions schwarz/mailqueue/mailflow_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def send_test_message(config_path, options):
msg_bytes = msg_as_bytes(check_msg)
msg = InMemoryMsg(msg_sender, (recipient,), msg_bytes)

# The idea of sending a test message is to provide immediate feedback so
# we use the SMTP transport only even when a queue directory might be set
# in the configuration file.
mh = MessageHandler(transports=(mailer,))
was_sent = mh.send_message(msg)
return was_sent
2 changes: 1 addition & 1 deletion tests/enqueue_message_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def path_maildir(tmp_path):
return os.path.join(str(tmp_path), 'mailqueue')


def test_can_create_missing_maildir_folders_before_enqueueing_message(path_maildir):
def test_can_create_missing_maildir_folders_before_enqueuing_message(path_maildir):
# important for regression test: mailqueue parent folder exists but
# "new"/"cur"/"tmp" are missing.
for sub_dir in ('new', 'cur', 'tmp'):
Expand Down
2 changes: 1 addition & 1 deletion tests/mq_sendmail_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def _create_alias_file(aliases, dir_path) -> str:
return str(aliases_path)


def test_mq_sendmail_with_queueing(ctx):
def test_mq_sendmail_with_queuing(ctx):
rfc_msg = _example_message(to='[email protected]')
unused_port = ctx.listen_port + 1
queue_dir = ctx.tmp_path / 'queue'
Expand Down

0 comments on commit d949935

Please sign in to comment.