Setting up Postfix as SMTP Client for Outgoing Automatic Mail Notifications

Say you want to get an e-mail notification when something goes wrong, or when some task finishes.

The alert category should actually be covered by some monitoring system like Prometheus or LibreNMS. But even then, the monitoring system itself needs to send e-mail notifications.

You may be tempted to use a simple tool like S-nail to send automated e-mails over an SMTP server. The trouble is, if the Internet connection goes down, the computer temporarily loses network connectivity, or the operating system decides to update itself and reboot, your notification e-mails can get lost. Some software may possibly retry, but the majority won't, or will not retry long enough.

The proper solution is to install a Mail Transfer Agent (MTA) that queues outgoing e-mails on disk and keeps retrying for a long time. Then any software can send mail with a local command-line tool like 'GNU mail', a POSIX 'mailx' utility, 'sendmail', or even a fully-featured application like 'Mutt'. Sending with a local tool is in fact rather common on Linux/Unix and does not actually require an SMTP server.

You would normally choose a small, simple server/daemon for this task. Examples are:


 * nullmailer
 * OpenSMTPD
 * msmtp together with some msmtpqueue add-on.

Unfortunately, I had no joy with them. If you actually succeeded with an alternative, please drop me a line.

So in the end, I turned to Postfix, which is a monster that has its own small service controller to orchestrate a number of subservices.

Your Linux distribution probably packages Postfix, so it is normally easy to install. The difficult part is to understand how it works, and to configure it.

This guide has been written for Ubuntu 20.04/Debian, but it should be easy to adapt to other Linux distributions.

On Ubuntu/Debian, you will be prompted for configuration after installing the 'postfix' package. If you missed it, you can always retrigger the configuration with command:

sudo dpkg-reconfigure postfix

The closest configuration to our needs is called:

Satellite system All mail is sent to another machine, called a 'smarthost', for delivery.

It does not really matter what you answer in the next questions, because you will be rewriting the generated configuration file anyway.

And now is when the fun begins.

The first thing to do is to locate the Postfix log, which tends to be at /var/log/mail.log. You want to keep watching the log with a command like this:

tail -f /var/log/mail.log

Any problems, including configuration warnings, will be output to that log.

Mail server folks consider themselves very important and unique, so that is why they named the main service/daemon "master". More humble individuals would have named it "postfix-small-systemd-clone", or something actually useful like "postfix-services-controller".

Mail server folks also believe that they know better, so Postfix automatically installs an SMTP service on your system. If you scan for listening sockets, you will see something like this:

$ sudo ss --listening --processes -A 'all,!unix,!netlink' tcp LISTEN  0  100  127.0.0.1:smtp  0.0.0.0:*  users:(("master",pid=1234,fd=13)) tcp LISTEN  0  100      [::1]:smtp     [::]:*  users:(("master",pid=1234,fd=14))

In the lines above, 'smtp' stands for TCP port 25, and 'master' is Postfix.

SMTP servers are relatively hard to secure, and completely useless in our scenario, so let's disable it. Edit configuration file /etc/postfix/master.cf and comment out a line like this:

smtp inet  n   -  y  -  -  smtpd

Then reload the configuration with a command like this:

sudo systemctl reload postfix

The listening sockets should be gone by now.

Mail server folks are fond of past times, so Postfix automatically installs a local mail service which nobody uses nowadays. In order to prevent services like 'cron' from filling up the disk with unread local mail notifications, it is best to disable it.

Edit configuration file /etc/postfix/main.cf and clear setting "mydestination", so that it looks like this:

mydestination =

After reloading the configuration, local mail delivery will stop. If you want, you can check that the local mailbox directory, usually at /var/mail, is empty or only has empty user mailbox files (0 bytes long).

Now it is time to configure the Postfix STMP client to relay all e-mails through an existing account on your ISP. This should get you going:

relayhost = [your-isp-smtp-server.example.com]:465 smtp_sasl_auth_enable = yes smtp_sasl_password_maps = static:some-existing-account-for-sending@example.com:your-password-here smtp_sasl_tls_security_options = noanonymous smtp_sender_dependent_authentication = no smtp_tls_wrappermode = yes smtp_tls_security_level = encrypt
 * 1) Required when using SMTPS wrappermode (TCP port 465):

The exact settings will depend on the kind of SMTP authentication that your ISP requires. Check out the full configuration file example below for more information on each configuration setting.

Do not forget to reload the Postfix configuration after every change:

sudo systemctl reload postfix

Before you test sending e-mails, make sure you have GNU mail installed, which is what this guide expects. The easiest way is to remove package 'bsd-mailx', if installed, and install package 'mailutils'. You can actually have both packages installed, and then wrestle with Debian's 'alternative' system, or just use 'mail.mailutils' instead of 'mail' when running the tool.

Now you can test sending an e-mail:

echo "Test 1 body" | mail --subject="Test 1" your-email-address@example.com

There is one security risk with this setup: anybody who can read file /etc/postfix/main.cf has access to the password for that SMTP account. On many systems, this tends to be anybody with a local account on the computer. Furthermore, depending on the mail provider, you may be able to impersonate anybody xxx@example.com when sending through any one yyy@example.com SMTP account. If you are worried about this, create a separate password file with tighter access permissions. Configuration setting smtp_sasl_password_maps will then look like this: smtp_sasl_password_maps = hash:/some/file You will have to use tool postmap, or use texthash:/some/file instead. There are plenty of guides about it on the Internet.

The last step is to set a proper sender address for such mails, in case they bounce, or your ISP's spam filter requires it:

sender_canonical_classes = envelope_sender, header_sender sender_canonical_maps = static:{ no-reply@example.com }

If you want to restrict the local user accounts which can submit mails, see configuration option 'authorized_submit_users'.

You may want to use a fixed recipient address like follows. Otherwise, local users can send e-mails to anybody:

recipient_canonical_classes = envelope_recipient, header_recipient recipient_canonical_maps = static:{ fixed-recipient@example.com }

That's it. Do not forget to reload the Postfix configuration after every change:

sudo systemctl reload postfix

This is a full example of configuration file /etc/postfix/main.cf with more information on each configuration setting:

compatibility_level = 2 inet_interfaces = loopback-only inet_protocols = all biff = no append_dot_mydomain = no mydestination = relayhost = [your-isp-smtp-server.example.com]:465 smtp_sasl_auth_enable = yes smtp_sasl_password_maps = static:some-existing-account-for-sending@example.com:your-password-here smtp_sasl_tls_security_options = noanonymous smtp_sender_dependent_authentication = no smtp_tls_wrappermode = yes smtp_tls_security_level = encrypt smtp_tls_CApath=/etc/ssl/certs smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache # # # sender_canonical_classes = envelope_sender, header_sender sender_canonical_maps = static:{ no-reply@example.com } recipient_canonical_classes = envelope_recipient, header_recipient recipient_canonical_maps = static:{ fixed-recipient@example.com }
 * 1) See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on fresh installs.
 * 2) Without this option set to 2, you'll get a warning in the log.
 * 1) We are not using stmpd, but just in case, specify loopback-only,
 * 2) because the default of 'all' is too dangerous.
 * 3) This setting may affect the IP source address for outbound mail
 * 4) that the Postfix SMTP client will use.
 * 1) The Internet protocols Postfix will attempt to use when making or accepting connections.
 * 2) 'all' means 'ipv4, ipv6', if this host implements IPv6.
 * 1) Do not use the local biff service to send "new mail" notifications to users
 * 2) who have requested them with the UNIX command "biff y".
 * 3) We are not using local mail delivery anway.
 * 1) Appending the eventually missing .example.com domain to recipient addresses is the MUA's job.
 * 2) Local senders will have to always specify full recipient addresses.
 * 1) Uncomment to generate "your message is delayed" notifications.
 * 2) In our scenario, where automated senders send mails, there is nobody to read
 * 3) any eventual e-mail notifications coming back anyway.
 * 4)   delay_warning_time = 4h
 * 1) Set to empty in order to disable local mail delivery.
 * 1) All outgoing mail will be routed through the following mail server (the so-called 'smarthost').
 * 1) There are ways to avoid specifying the password below as plain text.
 * 1) The default for smtp_sasl_security_options, and therefore
 * 2)             for smtp_sasl_tls_security_options too, is:
 * 3)   noplaintext, noanonymous
 * 4) However, if the connection is encrypted, allow plain-text passwords.
 * 5) Many SMTP servers require this.
 * 1) All local users will use the same account on the SMTP server to send mail.
 * 1) Required when using SMTPS wrappermode (TCP port 465).
 * 1) This forces TLS encryption when connecting to the remote SMTP server.
 * 2) It includes the STARTTLS ESMTP feature.
 * 3) It replaces older setting 'smtp_use_tls'.
 * 1) Local senders will have no valid sender e-mail address,
 * 2) so rewrite those invalid addresses in the outgoing mail.
 * 1) Only the e-mail address is replaced, not the display name.
 * 2) For example  "Some.Name "
 * 3) becomes then "Some.Name "
 * 1) Alternatively, you can set some bounces@example.com account so that the system administrator
 * 2) can read all delivery failure notifications. You may then want to set delay_warning_time too.
 * 1) Normally, the hostname is visible in the e-mail headers,
 * 2) so you can actually use a single sender address for all your computers.
 * 1) Rewrite the recipient address to a fixed e-mail address. Otherwise, local users can send
 * 2) e-mails to anybody, which you probably do not want to allow, just in case.
 * 3) This restriction should actually be set on the SMTP server relying the mail,
 * 4) that is, the server where account some-existing-account-for-sending@example.com lives,
 * 5) but some servers make it difficult to set such restrictions.