Command-Line Email with mailx, msmtp, and mutt
I configured command-line email so scripts and cron jobs can send notifications over authenticated SMTP using msmtp as the relay and mailx/mutt as front ends. Reliable script-driven email closed the loop on backups and monitoring without a heavyweight mail server.
Objective & Context
Automation needs a notification channel. This lab routes outbound mail through an authenticated SMTP relay with msmtp, then sends from scripts with mailx and attachments with mutt, complementing the cron and sysadmin labs.
Learning Objectives
- Configure msmtp as an authenticated SMTP relay client.
- Send plain-text alerts from shell scripts with mailx.
- Attach files to messages with mutt.
- Store credentials securely with restricted permissions.
- Wire email notifications into cron jobs and backup scripts.
Environment & Prerequisites
Prerequisites
- Linux with mailutils (mailx), msmtp, and mutt:
sudo apt install -y mailutils msmtp mutt. - SMTP relay credentials: host, port, username, and an app password with TLS.
- A test recipient mailbox you control.
- Basic shell scripting comfort from the scripting labs.
Step-by-Step Execution
1. Configure msmtp relay
# ~/.msmtprc
account default
host smtp.tyfsadik.org
port 587
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
user [email protected]
from [email protected]
passwordeval "secret-tool lookup service smtp user [email protected]"
chmod 600 ~/.msmtprc && msmtp --pretend -a default [email protected] < /dev/nullExpected output (config validation):
ignoring system configuration file /etc/msmtprc: No such file or directory
loaded user configuration file /home/taki/.msmtprc
using account default : host = smtp.tyfsadik.org, port = 587, ...
(envelope and SMTP dialog are printed; --pretend sends nothing)
What this teaches: msmtp is a sendmail-compatible SMTP client: the config declares host, port, TLS, and auth once, and every tool (mailx, cron, scripts) routes through it. Port 587 uses STARTTLS (tls_starttls on), which upgrades a plaintext connection; tls on without tls_starttls is for implicit TLS on port 465. 600 permissions are mandatory: msmtp refuses to run when the config is group or world readable. passwordeval fetches the app password from the login keyring so no secret sits in the file itself.
2. Send from a script
echo "backup completed" | mailx -s "Backup OK" [email protected]What this teaches: Piping message bodies into mailx with -s for the subject is the scriptable pattern; exit status 0 means the relay accepted it. This is how cron jobs and backups announce results.
3. Send with an attachment
echo "report attached" | mutt -s "Daily Report" -a report.pdf -- [email protected]; echo "exit=$?"Expected output:
(no output from mutt on success)
exit=0
What this teaches: mutt's -a attaches files and the double hyphen separates attachment paths from recipient addresses; batch mode is silent on success, so check the exit status. A 0 means msmtp handed the message to the relay successfully, and the relay logs will show the SMTP 250 OK acceptance.
How It Works
Unix mail follows the sendmail interface: programs hand a message to a Mail Transfer Agent, which routes it via SMTP. msmtp implements just the client side: it speaks SMTP with authentication and TLS to a relay (your provider or company server) and exits with a clear status. Because it honors the sendmail command interface, mailx, mutt, and cron can all use it without knowing SMTP details.
Separating the relay config from the sending tools is the key design win. Credentials live once in ~/.msmtprc with 600 permissions; scripts only say 'send this'. Rotating an app password then touches one file instead of every script, and msmtp --pretend lets you validate the handshake without delivering mail.
mutt is a full Mail User Agent for interactive and scripted use: it composes MIME messages, attaches files with correct encoding, and can also read mailboxes. For automation, its -s/-a batch flags turn report generation plus delivery into a single pipeline stage.
Validation & Testing
Trigger an email from a cron job and confirm delivery, headers, and attachment integrity. Pass criteria: mail is accepted by the relay (250 OK), arrives in the inbox, and attachments open correctly.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| msmtp: authentication failed | Wrong password, or the provider requires an app password | Generate an app-specific password and verify auth on is set with TLS |
| TLS certificate verification failed | Missing CA bundle or wrong tls_trust_file | Install ca-certificates and point tls_trust_file at the system bundle |
| mailx: cannot send, sendmail not found | No MTA configured as the sendmail interface | Point mailx at msmtp via /etc/mail.rc: set sendmail=/usr/bin/msmtp |
| Messages land in spam | Missing SPF/DKIM on the sending domain | Send through your provider's relay with auth rather than direct-to-MX |
| Attachment arrives corrupted | Wrong MIME encoding from manual construction | Let mutt build the MIME structure with -a instead of hand-rolling |
| Credential file readable by others | ~/.msmtprc created with default umask | Run chmod 600 ~/.msmtprc; msmtp warns when permissions are too open |
Advanced: Troubleshooting
- Auth failure: use an app-specific password and confirm TLS on port 587.
- Marked as spam: ensure SPF/DKIM align with the sending domain.
- No mail from cron: cron's minimal PATH may miss msmtp; use absolute paths.
Key Results
- Enabled reliable script-driven email over authenticated SMTP.
- Delivered job notifications and reports with attachments.
- Avoided running a full local MTA by relaying through msmtp.
- Verified deliverability with SPF/DKIM-aligned sending.
Key Takeaways
- msmtp is the SMTP relay client; mailx and mutt are the sending interfaces.
- One relay config serves every script; rotate credentials in one place.
- 250 OK from the relay is the delivery acceptance signal.
- Keep ~/.msmtprc at 600 permissions.
- Use app passwords, never main account passwords, for automation.
Practice Challenges
Challenge 1: Backup notifier
Goal: extend a backup script to email success with the archive size, or failure with the last 20 log lines, choosing the subject accordingly.
Hint: branch on $? after the backup step.
Challenge 2: Daily report mailer
Goal: cron a job that generates a CSV report and mutt-mails it as an attachment every morning at 07:00.
Hint: combine the cron lab's logged pattern with mutt -a.