An SMTP gateway for Apprise notifications.
Mailrise is an SMTP server that converts the emails it receives into Apprise notifications. The intended use case is as an email relay for a home lab or network. By accepting ordinary email, Mailrise enables Linux servers, Internet of Things devices, surveillance systems, and outdated software to gain access to the full suite of 60+ notification services supported by Apprise, from Matrix to Nextcloud to your desktop or mobile device.
Just as email brought written messages into the 21st century, Mailrise brings email notifications into the year 2021 and beyond. Compared to a conventional SMTP server, it's more secure, too—no more replicating your Gmail password to each of your Linux boxes!
"After a few very negative experiences with mail providers and their detection of spam accounts in the last year... F––k email. I will never set up a mail account for notifications. And with Mailrise I don't have to anymore!"
A Mailrise daemon is configured with a list of Apprise configuration files. Email senders encode the name of the desired configuration file into the recipient address. Mailrise then constructs the resulting Apprise notification(s) using the selected configuration.
A minimalist Mailrise configuration, for example, might contain a single Apprise configuration for Pushover:
configs: pushover: urls: - pover://[...]
And email senders would be able to select this configuration by using the recipient address:
[email protected]
By appending .<type>
to the username component of the address, it is also
possible to specify one of the four Apprise
notification types,
which, if the service you selected supports it, will change the color of the
icon of the resulting notification:
[email protected]
Email attachments will also pass through to Apprise if the addressed notification service(s) support attachments.
Mailrise is the sucessor to SMTP Translator, a previous project of mine that articulated a similar concept but was designed solely for Pushover.
An official Docker image is available
from Docker Hub. To use it, you
must bind mount a configuration file to /etc/mailrise.conf
. This mount must
be a file, not a directory, and it cannot override anything else in /etc.
Consumer NAS operating systems often conceal Docker's controls behind a GUI that is opaque and difficult to troubleshoot. There are some known gotchas when using Mailrise with one:
- Unraid can only passthrough directories, not files, making it impossible to
bind mount the mailrise.conf file. (While attempting to do so, some users have
accidentally passed through their entire /etc directory, thereby completely
breaking Python.) To fix this, override the
image's default command with something like
-v /etc/myconfig/mailrise.conf
so you can passthrough themyconfig
directory without interfering with the rest of the filesystem. - TrueNAS SCALE runs containers as root by default. This breaks Mailrise, which
is designed to run as a non-root container for enhanced security. Ensure the
container is running as user
999
and group999
.
You can find Mailrise on PyPI. The minimum Python version is 3.8+.
Once installed, you should write a configuration file and then configure Mailrise to run as a service. Here is the suggested systemd unit file:
[Unit] Description=Mailrise SMTP notification relay [Service] ExecStart=/usr/local/bin/mailrise /etc/mailrise.conf [Install] WantedBy=multi-user.target
This repository is structured like any other Python package. To install it in editable mode for development or debugging purposes, use:
pip install -e .
To build a wheel, use:
tox -e build
If you are using Visual Studio Code, a development container is included with all the Python tooling necessary for working with Mailrise.
The mailrise
program accepts a path to a YAML configuration file that
encapsulates the daemon's entire configuration. The root node of this file should
be a dictionary. Mailrise accepts the following keys (periods denote
sub-dictionaries):
Key | Type | Value |
---|---|---|
configs.<name> | dictionary |
fnmatch pattern
matching tokens are also accepted here, though they are considered special
characters in YAML and therefore must be enclosed in quoted strings.
Please also note that the domain component still defaults to
Please note that the period character is reserved for sender flags, so it
cannot be used in the username component of the address.
The dictionary value is the Apprise YAML configuration itself, exactly as it would be specified in a standalone file for Apprise. In addition to the Apprise configuration, some Mailrise-exclusive options
can be specified under this key. See the |
configs.<name>.mailrise.title_template | string | The template string used to create notification titles. See "Template strings" below. Defaults to |
configs.<name>.mailrise.body_template | string | The template string used to create notification body texts. See "Template strings" below. Defaults to |
configs.<name>.mailrise.body_format | string | Sets the data type for notification body texts. Must be If not specified here, the data type is inferred from the body part of the
email message. So if you have your body template set to anything but the
default value of |
import_code | string | Allows advanced users to supply their own Python code to replace key
components of Mailrise. Place the path to the Python source file here.
See "Custom routers and authenticators" below. Custom routers ignore any
data in the configs section, and custom authenticators ignore any
data in the smtp.auth section. |
listen.host | string | Specifies the network address to listen on. Defaults to all interfaces. |
listen.port | number | Specifies the network port to listen on. Defaults to 8025. |
tls.mode | string | Selects the operating mode for TLS encryption. Must be Defaults to off. |
tls.certfile | string | If TLS is enabled, specifies the path to the certificate chain file. This
file must be unencrypted and in PEM format. For testing purposes, you can
use the openssl command to
create
a self-signed certificate. |
tls.keyfile | string | If TLS is enabled, specifies the path to the key file. This file must be
unencrypted and in PEM format. For testing purposes, you can use the
openssl command to
create
a self-signed certificate. |
smtp.auth.basic | dictionary | Enables basic authentication with a static username and password list. Each entry in the dictionary represents a valid login. The key is the username, while the value is the password. Note that credentials will be sent over plaintext unless some form of TLS is enabled. |
smtp.hostname | string | Specifies the hostname used when responding to the EHLO command. Defaults to the system FQDN. |
You can use Python's template strings to specify
custom templates that Mailrise will construct your notifications from. Templates
make use of variables that communicate information about the email message. Use
dollar signs ($
) to insert variables.
The following variables are available for both title and body templates:
Identifier | Value |
---|---|
subject | The email subject. |
from | The sender's full address. |
body | The full contents of the email body. |
to | The full email address of the selected Apprise configuration. |
config | The name of the selected Apprise configuration, unless it uses a custom domain, in which case this is equivalent to the "to" variable. |
type | The class of Apprise notification. This is "info", "success", "warning", or "failure". |
If you are new to YAML syntax, you may find the Online YAML Parser, which converts YAML syntax to the underlying JSON structure, a useful aid.
configs:
# You can send to this config with "[email protected]".
#
# The "-" is *very* important, even when configuring just a single URL.
# Apprise requires urls to be a YAML *list*.
#
basic_assistant:
urls:
- hasio://HOST/ACCESS_TOKEN
# You can send to this config with "[email protected]".
#
telegram_and_discord:
urls:
- tgram://MY_BOT_TOKEN
- discord://WEBHOOK_ID/WEBHOOK_TOKEN
# You can also control the layout of the message with custom template
# strings.
mailrise:
title_template: "Urgent: ${body}"
body_template: ""
body_format: text
# You can send to this config with "[email protected]".
#
[email protected]:
urls:
- pover://USER_KEY@TOKEN
# We also support wildcards with the fnmatch library; see
# https://docs.python.org/3/library/fnmatch.html for the full syntax.
#
# YAML requires characters like "*" and "[" to be enclosed in quoted
# strings.
#
# This pattern matches addresses like "[email protected]"
# and "[email protected]".
#
"awesome*@mycooldomain.com":
urls:
- pover://USER_KEY@TOKEN
# Of course, it's also possible to pattern match by the domain.
#
"my_cool_name@*.net":
urls:
- pover://USER_KEY@TOKEN
# Wildcard targets are evaluated in the order they appear in the
# configuration file, and Mailrise uses the first match. So, this config
# will catch any addresses not matched by the previous targets.
#
# Note that if you use "*" as your pattern, Mailrise will expand that to
# "*@mailrise.xyz", which is probably not the catch-all target you wanted.
#
"*@*":
urls:
- discord://WEBHOOK_ID/WEBHOOK_TOKEN
# You can also insert environment variables, a feature lifted directly
# from Home Assistant. This is useful for reading secrets from
# container orchestrators like Kubernetes.
- !env_var MY_SECRET_URL
# Finally, you can enable TLS encryption and/or SMTP authentication if you
# want them.
tls:
mode: starttls
certfile: /path/to/certificate.pem
keyfile: /path/to/privatekey.pem
smtp:
auth:
basic:
username: password
AzureDiamond: hunter2
Given the popularity of Let's Encrypt, it can be a pain to get Mailrise to work with automatic certificate renewals. For easy TLS setup, I recommend running Mailrise in plaintext mode while using a fully-featured ACME client like Traefik to handle encryption for you.
docker-compose.yml:
mailrise:
image: yoryan/mailrise
container_name: mailrise
restart: unless-stopped
volumes:
- ./mailrise.conf:/etc/mailrise.conf:ro
labels:
traefik.tcp.routers.mailrise.rule: "HostSNI(`*`)"
traefik.tcp.routers.mailrise.tls: "true"
traefik.tcp.routers.mailrise.tls.certresolver: "letsencrypt"
traefik.tcp.routers.mailrise.tls.domains[0].main: "my.public.mailrise.domain.com"
traefik.tcp.routers.mailrise.tls.domains[0].sans: ""
traefik.tcp.routers.mailrise.entrypoints: "mailsecure"
traefik.yml:
entryPoints:
mailsecure:
address: ":465"
certificatesResolvers:
letsencrypt:
# ...
SMTP clients can then connect to my.public.mailrise.domain.com, on port 465, using the TLS-on-connect mode.
If you are handy with Python and want to overcome the limitations of the
configuration format, you can replace Mailrise's notification
routing and SMTP authentication logic with your own. Use the import_code
directive in your configuration file with the path to a Python source file.
The router class, if provided, should be stored in a module-level variable named
router
. The authenticator callback, if provided, should be stored in a
module-level variable named authenticator
.
For further details, refer to the sample file used for testing, the Mailrise router API, and the aiosmtpd authenticator callback.