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
sudoprivileges. - 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.comwith a Google app password, orsmtp.sendgrid.netwith the userapikeyand an API key. - A sender address on a domain your provider allows you to send from, referred to as
alerts@your_domain.
NoteSending directly to recipients on port 25 from a server IP usually ends up in spam or is blocked. Relaying through an authenticated provider on port 587 avoids both problems.
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:
- For General mail configuration type, choose Satellite system.
- For System mail name, keep the server host name (for example
web01.your_domain). - 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. - 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.
