The mail command (mailx) lets scripts and cron jobs send email with a single line, which makes it the simplest way to get alerts out of a server. On its own it only hands the message to a local mail transfer agent, so you also need a correctly configured Postfix that relays mail through an authenticated SMTP provider. In this tutorial you will install mailx and a send-only Postfix relay on Ubuntu 24.04, route system mail for root to your inbox, and use it from a script and from cron.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS. Debian 12 uses the same packages.
  • A non-root user with sudo privileges.
  • SMTP credentials from an email provider or transactional email service: host name, port (usually 587), user name and password or API key. Examples: smtp.gmail.com with a Google app password, or smtp.sendgrid.net with the user apikey and an API key.
  • A sender address on a domain your provider allows you to send from, referred to as alerts@your_domain.

Step 1 - Installing Postfix as a send-only relay

Install Postfix and the SASL modules it needs to authenticate against the provider:

sudo apt update
sudo apt install postfix libsasl2-modules

The installer opens a configuration dialog:

  1. For General mail configuration type, choose Satellite system.
  2. For System mail name, keep the server host name (for example web01.your_domain).
  3. For SMTP relay host, enter your provider in the form [smtp.example.com]:587. The square brackets tell Postfix to connect to that host directly instead of looking up MX records.
  4. Accept the defaults for the remaining questions.

If you skipped the dialog, run sudo dpkg-reconfigure postfix to open it again. Check that Postfix is running:

systemctl is-active postfix
active

Step 2 - Configuring SMTP authentication and TLS

Store the provider credentials in a map file. The host and port must match the relayhost value exactly, including the brackets:

sudo nano /etc/postfix/sasl_passwd
[smtp.example.com]:587 your_smtp_user:your_smtp_password

Protect the file and compile it into the lookup table Postfix reads:

sudo chmod 0600 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd

postmap creates /etc/postfix/sasl_passwd.db, which inherits the restricted permissions. Now enable authentication, require TLS for the connection to the relay, and make sure Postfix only listens on localhost so the server can never act as an open relay:

sudo postconf -e \
  'relayhost = [smtp.example.com]:587' \
  'smtp_sasl_auth_enable = yes' \
  'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd' \
  'smtp_sasl_security_options = noanonymous' \
  'smtp_tls_security_level = encrypt' \
  'inet_interfaces = loopback-only'

Changing inet_interfaces requires a full restart, not a reload:

sudo systemctl restart postfix

Confirm the effective settings:

postconf relayhost smtp_tls_security_level inet_interfaces
relayhost = [smtp.example.com]:587
smtp_tls_security_level = encrypt
inet_interfaces = loopback-only

Step 3 - Rewriting the sender address

By default, mail sent by root goes out as [email protected]_domain, which most providers reject or mark as spam because it is not a verified sender. A generic map rewrites local addresses to your real sender address when mail leaves the server:

sudo nano /etc/postfix/generic

Replace web01.your_domain with the system mail name you set in Step 1:

@web01.your_domain    alerts@your_domain

Compile the map and enable it:

sudo postmap /etc/postfix/generic
sudo postconf -e 'smtp_generic_maps = hash:/etc/postfix/generic'
sudo systemctl reload postfix

Step 4 - Installing mailx and sending a test message

Ubuntu offers several mail implementations. bsd-mailx is small and provides both mail and mailx:

sudo apt install bsd-mailx

Send a test message. Replace [email protected] with the address that should receive alerts:

echo "Test message from $(hostname) at $(date)" | mail -s "Test from $(hostname)" [email protected]

mail returns immediately because Postfix queues the message. Check the Postfix log to confirm delivery to the relay:

sudo journalctl -t postfix/smtp -n 5 --no-pager
Sep 25 15:10:42 web01 postfix/smtp[5123]: 4XyZ1c2d3e: to=<[email protected]>, relay=smtp.example.com[203.0.113.25]:587, delay=1.2, delays=0.02/0.01/0.6/0.57, dsn=2.0.0, status=sent (250 2.0.0 OK)

status=sent means the provider accepted the message. Check your inbox, including the spam folder the first time. If the status is deferred or bounced, the text in parentheses explains why; see the Troubleshooting section.

To set a display name or reply address, add headers with -a:

echo "Disk check passed" | mail -s "Daily report" -a "From: Server Alerts <alerts@your_domain>" [email protected]

Step 5 - Forwarding mail for root

Many system tools (cron, unattended-upgrades, smartd, mdadm) send their warnings to root. Forward that mail to a real mailbox by editing the aliases file:

sudo nano /etc/aliases

Add or change the root line:

postmaster: root
root: [email protected]

Rebuild the aliases database:

sudo newaliases

Test it by writing to root:

echo "Root alias test" | mail -s "Root alias test" root

The message should arrive at [email protected], and the log shows to=<[email protected]>, orig_to=<root>.

Step 6 - Sending alerts from scripts and cron

With mail working, any script can send an alert by piping text to mail. The following example checks the root filesystem and sends an email when usage passes 90 percent:

sudo nano /usr/local/sbin/disk-alert
#!/usr/bin/env bash
set -euo pipefail

LIMIT=90
RECIPIENT="[email protected]"

used="$(df --output=pcent / | tail -n 1 | tr -dc '0-9')"

if (( used >= LIMIT )); then
    {
        echo "Root filesystem on $(hostname) is ${used}% full (limit ${LIMIT}%)."
        echo
        df -h /
        echo
        echo "Largest directories under /var:"
        du -xh --max-depth=1 /var 2>/dev/null | sort -rh | head -n 10
    } | mail -s "[ALERT] $(hostname): disk ${used}% full" "$RECIPIENT"
fi

Make it executable and schedule it hourly in root's crontab:

sudo chmod 0755 /usr/local/sbin/disk-alert
sudo crontab -e
[email protected]
0 * * * * /usr/local/sbin/disk-alert

The MAILTO line is useful on its own: cron emails any output or error a job prints to that address. A script that stays silent on success and writes to stderr on failure gets alerting for free, without calling mail at all.

To test the script without filling the disk, run it once with a lower limit by temporarily editing LIMIT=1, then set it back.

Troubleshooting

Inspect queued messages with mailq and retry them after fixing the cause with sudo postqueue -f. The most common errors in journalctl -t postfix/smtp are:

SASL authentication failed or 535 Authentication failed: the user or password in /etc/postfix/sasl_passwd is wrong, or the provider requires an app password or API key. Fix the file, run sudo postmap /etc/postfix/sasl_passwd again and reload Postfix.

no mechanism available: the libsasl2-modules package is missing. Install it and restart Postfix.

Connection timed out to the relay: outbound traffic on port 587 is blocked by a firewall. Test with nc -vz smtp.example.com 587.

550 Sender address rejected or 553: the provider does not accept the sender. Check that /etc/postfix/generic maps to an address or domain verified in your provider account.

mail: command not found: install bsd-mailx as in Step 4.

Conclusion

Your server now relays mail through an authenticated SMTP provider over TLS, rewrites the sender to a verified address, forwards system mail for root, and lets any script or cron job send alerts with mail. As next steps, publish SPF and DKIM records for your_domain as your provider describes to improve deliverability, add MAILTO to existing cron jobs, and consider a webhook or chat channel for alerts that need faster attention than email.