One of the most useful things I’ve automated with Bash is email alerts — a backup script that emails me if it fails, a monitoring job that reports disk usage, a cron job that sends me a daily summary. Sending email from a shell script sounds like it should be complicated, but once I understood the handful of tools available, it became one of the simplest additions to any script. Here’s everything I’ve learned about sending email from Bash.
The Tools Available
There are a few common ways to send email from a Bash script, and I pick based on what’s already installed and how much control I need:
mail/mailx: the classic, simplest command-line mail tool, good for quick plain-text alerts.sendmail: lower-level, lets you construct the full email including headers.ssmtp/msmtp: lightweight SMTP clients, useful when you need to relay through an external SMTP server like Gmail or SendGrid.curlwith SMTP support: useful when you want to send email without installing a dedicated mail tool at all.
Installing mailutils
On most Debian/Ubuntu systems:
sudo apt-get install mailutils
On RHEL/CentOS/Fedora:
sudo dnf install mailx
Beginner Example: Sending a Simple Email
echo "This is the email body" | mail -s "Test Subject" recipient@example.com
echo "..."produces the body of the email, piped intomail.-s "Test Subject"sets the subject line.- The final argument is the recipient’s address.
By default, this relies on a local mail transfer agent (MTA) like postfix or sendmail being installed and configured to actually deliver the message, which is often the missing piece when this “doesn’t work” on a fresh server.
Sending Email with an Attachment
echo "Please find the report attached." | mail -s "Daily Report" -A /tmp/report.pdf recipient@example.com
The -A flag (supported by mailutils‘ mail, spelled differently in some mailx variants) attaches a file to the message.
Step-by-Step: A Script That Emails a System Report
#!/usr/bin/env bash
set -euo pipefail
RECIPIENT="admin@example.com"
SUBJECT="Daily System Report - $(hostname) - $(date +%Y-%m-%d)"
REPORT_FILE=$(mktemp)
{
echo "System Report for $(hostname)"
echo "Generated: $(date)"
echo ""
echo "Disk Usage:"
df -h
echo ""
echo "Memory Usage:"
free -h
echo ""
echo "Uptime:"
uptime
} > "$REPORT_FILE"
mail -s "$SUBJECT" "$RECIPIENT" < "$REPORT_FILE"
rm -f "$REPORT_FILE"
Explaining the Script Internally
- The
{ ... } > "$REPORT_FILE"block groups multiple commands and redirects all their combined output into a single file, which is cleaner than redirecting each command individually. mktempcreates a temporary file with a unique, safe name, avoiding collisions if the script runs concurrently or the report file is left over from a previous run.mail -s "$SUBJECT" "$RECIPIENT" < "$REPORT_FILE"uses input redirection (<) instead of a pipe, feeding the file’s contents directly tomailas the message body.- Cleaning up with
rm -f "$REPORT_FILE"at the end avoids leaving temporary files scattered across/tmp.
Sending Email Through an External SMTP Server (Gmail Example)
Many servers don’t have a properly configured local MTA, so relaying through an external SMTP provider like Gmail, SendGrid, or Amazon SES is often more reliable. msmtp is my preferred tool for this because its configuration is straightforward.
Install it:
sudo apt-get install msmtp msmtp-mta
Configure ~/.msmtprc:
account default
host smtp.gmail.com
port 587
auth on
user your_email@gmail.com
password your_app_password
tls on
tls_starttls on
from your_email@gmail.com
logfile ~/.msmtp.log
Set proper permissions since this file contains credentials:
chmod 600 ~/.msmtprc
Send an email:
echo -e "Subject: Test via msmtp\n\nThis is the body of the email." | msmtp recipient@example.com
Note the \n\n — this separates the Subject: header from the message body, matching the raw structure of an email message (headers, blank line, body).
Real-World Use Case: Alerting on Failed Backups
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/data"
DEST="/backups/backup-$(date +%Y%m%d).tar.gz"
RECIPIENT="admin@example.com"
if tar -czf "$DEST" "$BACKUP_DIR"; then
echo "Backup succeeded: $DEST" | mail -s "Backup Success" "$RECIPIENT"
else
echo "Backup FAILED at $(date)" | mail -s "URGENT: Backup Failure" "$RECIPIENT"
exit 1
fi
This pattern — success email on the happy path, urgent email on failure — is one I copy into almost every unattended maintenance script I write.
Automation Example: Weekly Digest Email with HTML Formatting
Plain text is fine for alerts, but for a nicer weekly summary I send HTML email using proper MIME headers:
#!/usr/bin/env bash
set -euo pipefail
RECIPIENT="team@example.com"
SUBJECT="Weekly Server Digest"
{
echo "To: $RECIPIENT"
echo "Subject: $SUBJECT"
echo "MIME-Version: 1.0"
echo "Content-Type: text/html"
echo ""
echo "<html><body>"
echo "<h2>Weekly Digest</h2>"
echo "<p>Uptime: $(uptime -p)</p>"
echo "<p>Disk usage:</p><pre>$(df -h)</pre>"
echo "</body></html>"
} | msmtp -t
The -t flag tells msmtp to read the recipient(s) from the To: header inside the message itself rather than requiring it as a command-line argument.
Security Considerations
- Never hardcode plaintext passwords directly in a script. Store SMTP credentials in a config file like
~/.msmtprcwithchmod 600permissions, or better yet, use a secrets manager and inject the password as an environment variable at runtime. - Use TLS/STARTTLS for any SMTP relay connection; sending credentials or message content over plaintext SMTP exposes them to network eavesdropping.
- Be cautious with email content built from untrusted input. If any part of the email body comes from user input or scraped data, sanitize it to avoid header injection attacks (where a newline followed by crafted headers could let an attacker manipulate recipients or add BCC fields).
- Rate-limit automated emails. A misconfigured monitoring script that emails on every check instead of only on state changes can quickly get your sending domain flagged as spam, or worse, get your account suspended by the SMTP provider.
- Use app-specific passwords for services like Gmail rather than your main account password, and revoke them if a script or server is ever compromised.
Optimization Tips
- Batch related alerts into a single email instead of sending one email per event, especially for high-frequency checks — nobody wants fifty emails about the same disk filling up.
- Use a local MTA (like
postfixconfigured as a relay-only null client) if you’re sending a high volume of email from multiple scripts on the same server, rather than configuring SMTP credentials separately in every script. - Log every send attempt (
msmtp‘slogfilesetting is great for this) so you can debug delivery issues without guessing.
Troubleshooting
- “mail: command not found”: install
mailutils(Debian/Ubuntu) ormailx(RHEL/CentOS). - Email accepted by
mailbut never arrives: this usually means there’s no properly configured local MTA to actually relay the message. Check/var/log/mail.logfor delivery errors, or switch to an external SMTP relay likemsmtp. - “Authentication failed” with msmtp: double-check the app-specific password (not your regular account password) and confirm 2FA-related settings on the email provider’s side.
- Emails land in spam: make sure your sending domain has proper SPF, DKIM, and DMARC records if you’re sending through your own domain rather than a well-known provider.
Common Mistakes to Avoid
- Assuming
mail“just works” without a properly configured MTA behind it. - Hardcoding SMTP passwords in plaintext inside a script that gets committed to version control.
- Sending an email for every single script run instead of only on meaningful state changes, leading to alert fatigue.
- Forgetting the blank line between headers and body when manually constructing raw email messages, which breaks MIME parsing.
FAQs
Do I need a local MTA installed to send email from Bash? Not necessarily — you can relay directly through an external SMTP server using msmtp or ssmtp, which avoids needing to configure a full mail server like Postfix locally.
Can I send email with attachments using msmtp? msmtp itself doesn’t handle MIME attachment encoding; combine it with mutt or construct the MIME multipart message manually if attachments are needed.
Is it safe to use Gmail’s SMTP for automated scripts? Yes, with an app-specific password and TLS enabled, though be aware Gmail enforces sending limits that may not suit high-volume automated alerts.
How do I test email sending without spamming a real inbox? Tools like mailhog or mailtrap.io provide fake SMTP servers for testing that capture emails without actually delivering them anywhere.
Summary
Sending email from Bash is a small addition that makes scripts dramatically more useful — turning a silent cron job into something that actively tells you when it succeeds, fails, or needs attention. Whether through a simple mail command backed by a local MTA or a proper SMTP relay via msmtp, the pattern is the same: build the message, set the right headers, and let the tool handle delivery, while keeping credentials and content secure along the way.
References
- GNU Mailutils manual: https://www.gnu.org/software/mailutils/manual/mailutils.html
- msmtp official documentation: https://marlam.de/msmtp/documentation/
- Postfix documentation: https://www.postfix.org/documentation.html
- RFC 5321 (Simple Mail Transfer Protocol): https://www.rfc-editor.org/rfc/rfc5321
