Mini SMTP forwarding relay
Simple python script that locally accepts any email and sends them to a remote server, unifying configuration and authentication without needing a real MTA.
Configuring a fully-fledged MTA for sending emails or even just for forwarding is typically a non-trivial task. In especially local scripts, website forms, or alerts from monitoring tools (e.g., Grafana) require some sort of SMTP relay – or need to be supported and configured individually. The minimal forwarding relay script accepts unsolicited mails via SMTP and sends them using a remote server as configured. This allows for a straight-forward setup, central upstream configuration, and trivial configuration for the actual use-cases, for example without real credentials needed.
Maintaining an own mailserver with a custom domain is rather complex, in especially when trying to avoid mistakenly being flagged as spam. Therefore, domain providers commonly offer a corresponding SMTP endpoint. Also, it is often acceptable to simply use a freemail account for sending emails.
The “traditional” way for sending such emails from a Linux machine is the postfix MTA, which however involves numerous daemons to run.
Also, even though examples for use-cases such as internet site or satellite system exist, it is hard to configure and reads several system files to be adapted or checked accordingly.
The basic CLI tools mail
/sendmail
also pull in huge dependencies and are similarly complex to configure and setup – using msmtp can be a convenient alternative for scripts.
So the goal here is a simple SMTP relay without postfix and only the expected configuration overhead: Where to listen for SMTP and where to forward to.
Usage
The self-contained python script can be invoked directly or installed as package, see below for installation hints. Only a configuration file must be provided:
usage: smtprd [-h] [--config CONFIG.INI]
Minimal SMTP forwarding relay daemon.
Accepts unsolicited mails via SMTP and sends them using a remote SMTP server as configured.
optional arguments:
-h, --help show this help message and exit
--config CONFIG.INI configuration file (default: ./config.ini)
Shutdown can be initiated via SIGINT
(KeyboardInterrupt, Ctrl-C) or sending SIGTERM
.
Configuration
A minimal, self-explanatory configuration file example in standard Python INI syntax could look like:
# [server]
# hostname = localhost
# port = 8025
[client]
hostname = smtp.mail.example.com
port = 465
username = foo@example.com
password = s3cr3t
sender = foo@example.com
recipients = monitoring.foo@example.com, John Foobar <foo@baz.org>
# set_reply_to = false
# use_tls = true
# start_tls = false
Please note that the aiosmtplib client currently only supports username/password-based authentication methods (cram-md5
, plain
, login
).
Most mail servers should support and allow these for encrypted connections.
Otherwise, a so-called app password might be supported as fallback mechanism, and can be used just like in any generic mail client software.
On the other hand, the smtprd
server-side does not require or support authentication or encryption for itself.
After all, it should ease local configuration by centrally deploying credentials and is not meant to be publicly available anyway for numerous reasons.
The server listening on any standard SMTP port below 1024 will require running as root
, thus the default 8025.
Examples
A local msmtp client can be configured to use smtprd
via /etc/msmtprc
:
account default
host localhost
port 8025
tls off
tls_starttls off
auth off
from msmtp.mail@example.com
syslog LOG_MAIL
Shell scripts are then able to easily send arbitrary mails:
MESSAGE='Hello, world!'
msmtp scriptwatch@example.com <<-EOF
Subject: Hello
$MESSAGE
EOF
Another usecase that profits from central configuration could be /etc/grafana/grafana.ini
:
[smtp]
enabled = true
host = localhost:8025
from_address = grafana@example.com
The default alert contact point can then be used to send mails to any – c.f., the smtprd
– recipients.
Background
During the forwarding process, i.e., after receiving the mail to be sent and before sending it upstream, the following headers are changed:
From
- Replaced or added with the configured
sender
. To
- Replaced or added with the configured
recipients
list. Reply-To
- Added if not already present with the originally specified sender, if configured so. An unknown address here might not be acceptable for some servers.
Original-Sender
- The sender as originally specified but overridden by the configured
sender
. Original-Recipient
- The recipients list as originally specified but overridden by the configured
recipients
. Date
- Added if not already present.
Message-ID
- Added with a random UUID if not already present.
Received
- Containing the peer and local IP address with hostname and timestamp.
X-Peer
- The peer IP address and port.
So only a Subject
and the actual body must be given for a valid message.
As the configured sender and receiver(s) always take precedence, the original ones can be chosen arbitrarily (except for when explicitly configured to be added as Reply-To
).
Installation
Everything is self-contained in a single executable python script that can thus be invoked directly (tested for Python 3.8).
The only dependencies are aiosmtpd
and aiosmtplib
, as stated in requirements.txt
.
As a more convenient method, the packaged script can also be installed system- or user-wide (the latter without sudo
):
sudo make install # pip install .
sudo make uninstall # pip uninstall smtprd
For development, simply calling make
will run in a local virtual environment.
For typing and linter checks, make check
is provided.
If smtprd
should run per default, a systemd unit file such as /etc/systemd/system/smtprd.service
can be used:
[Unit]
Description=SMTP Relay Daemon
[Service]
User=mail
Group=mail
Type=exec
ExecStart=/usr/local/bin/smtprd --config /etc/smtprd.ini
Environment=PYTHONUNBUFFERED=TRUE
[Install]
WantedBy=multi-user.target