How to Send Email from Bash

How to Send Email from Bash

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.
  • curl with 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 into mail.
  • -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 mailutilsmail, 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.
  • mktemp creates 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 to mail as 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 ~/.msmtprc with chmod 600 permissions, 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 postfix configured 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‘s logfile setting is great for this) so you can debug delivery issues without guessing.

Troubleshooting

  • “mail: command not found”: install mailutils (Debian/Ubuntu) or mailx (RHEL/CentOS).
  • Email accepted by mail but never arrives: this usually means there’s no properly configured local MTA to actually relay the message. Check /var/log/mail.log for delivery errors, or switch to an external SMTP relay like msmtp.
  • “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

Total
2
Shares

Leave a Reply

Previous Post
How to Access System Information in Bash

How to Access System Information in Bash

Next Post
How to Create a Bash Web Scraping Tool

How to Create a Bash Web Scraping Tool

Related Posts