Victor is a full stack software engineer who loves travelling and building things. Most recently created Ewolo, a cross-platform workout logger.
Setup postfix on Ubuntu and send emails for failed cron jobs

Recently, I learned that on linux cron comes with a very simple email notification setup, namely the MAILTO environment variable. In this article we will setup postfix to send email via an external smtp server and ask cron to send emails on failed jobs. This requires that we have a domain e.g. domain.net that we can use to send emails as.

Smtp host

To begin with, we will need an smtp host that lets us send emails. While one can use gmail, it requires a fair amount of hoop jumping to enable access for less secure apps and needs to constantly be used otherwise it is disabled automatically. I can recommend migadu for their barebones plan that lets one configure multiple domains and senders. For the purposes of this article, we only need 1 domain that we can send emails from. Once a smtp provider is configured and a sender username/password obtained, we can proceed.

Note that if using migadu, it is important to setup a mailbox for the machine name that will be sending the emails as well as configuring subdomain wildcards sending. This is because on my machine atleast cron sends the emails as user@host.domain.net and with subdomain wildcards any email from/to subdomain.domain.net gets put into the subdomain@domain.net mailbox.

Setup postfix
Install: sudo apt install postfix mailutils configure the smtp relay in /etc/postfix/main.cf:
# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = subdomain.domain.net
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = $myhostname, domain.net, localhost.localdomain, localhost
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
#default_transport = error
relay_transport = error
inet_protocols = ipv4

relayhost = [smtp.migadu.com]:465
smtp_sasl_auth_enable=yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_use_tls = yes

# following 2 are required by migadu
smtp_tls_wrappermode = yes
smtp_tls_security_level = encrypt

Note that most other smtp relays will require the relayhost to be [mail.example.com]:587 with the tls wrapper mode and security level left at their defaults.

Now configure the smtp password via /ec/postfix/sasl_passwd (only need one that matches the entry in relayhost):

# Gmail: 
[smtp.gmail.com]:587 user@example.org:password

# Mailgun: 
[smtp.mailgun.org]:587 user@example.com:password

# Amazon SES: 
[email-smtp.us-east-1.amazonaws.com]:587 user:pass

Hash the password: sudo postmap /etc/postfix/sasl_passwd and restart the service sudo service postfix restart.

Test sending emails via echo "Hello world!" | mail -s "email test $(date +%s)" vic@smalldata.tech. Note that mail takes a parameter -r that lets one change the sender address. To check for errors, see /var/log/mail.log. Fortunately, email is a very old system and smtp providers actually provide useful error messages when things don't work! Remember that the postfix service needs to be restarted on any change to the main configuration file.

Most email providers generally overwrite the sender address based on the account that is used to log in, however migadu specifically requires that the correct sender address be used. Normally postfix simply uses user@hostname as the sender address so it might be required to rewrite it to something more legitimate:


echo "user@hostname subdomain@domain.net" | sudo tee -a /etc/postfix/generic 
echo "smtp_generic_maps = hash:/etc/postfix/generic" | sudo tee -a /etc/postfix/main.cf
sudo postmap /etc/postfix/generic
sudo service postfix restart
Cron alerts

The last piece is to set the MAILTO variable at the start of the cron configuration and thereafter setup the jobs as the following which will send regular output to the specified file and email the output of any failure:

MAILTO=vic@smalldata.tech

# run every evening at 22:06
6 22 * * * /home/user/bin/backup.sh > /home/user/cron-logs/backup.log

If the automatic email is not sent out, one can also force output to be sent as email via: 2>&1 | mail -s "subject" box@mailbox.com.

Besides cron job notifications, we can now send emails programmatically from our system which is pretty cool :). Happy emailing!