2026年9月14日

2026年9月14日

シェルスクリプトでメール送信する方法(sendmail・mailx・msmtp・curlの使い分けと日本語件名)

シェルスクリプトからメールを送るには、サーバーに Postfix などのメール転送エージェント(MTA)が入っていれば sendmail -t にヘッダー付きの本文を渡す方法が最も扱いやすく、MTA を置きたくない場合は msmtp や curl で外部の SMTP サーバーへ直接送る方法が向いています。この記事では、それぞれの方法の使い分け、日本語の件名が文字化けしない書き方、cron から通知を送るときの注意点、届かないときの確認手順を説明します。バックアップ完了やディスク残量の通知など、サーバー運用の自動通知を作りたい方を想定しています。

まず確認すること:どの経路でメールが出ていくか

コマンドを選ぶ前に、送ったメールが最終的にどこから出ていくかを決めておくと迷いません。

  • サーバー上の MTA から直接送る: Postfix などが動いていて、送信元ドメインの SPF や DKIM がそのサーバー向けに設定されている場合です。sendmail や mail コマンドはこの MTA にメールを渡すだけで、配送は MTA が行います。
  • 外部の SMTP サーバーを経由する: Gmail、Microsoft 365、SendGrid などの SMTP に認証して送る場合です。VPS では送信用の 25 番ポートが既定で塞がれていることが多く、587 番(STARTTLS)や 465 番(SMTPS)で外部サービスを使う構成が一般的です。

次のコマンドで、MTA が入っているかどうかと、sendmail の実体を確認できます。

command -v sendmail mail mailx msmtp curl
ls -l "$(command -v sendmail)"
systemctl is-active postfix 2>/dev/null

sendmail は Postfix や Exim が互換コマンドとして提供していることが多く、名前は同じでも中身は MTA ごとに異なります。どれも入っていなければ、MTA を構築するよりも msmtp か curl で外部 SMTP を使うほうが手間は少なく済みます。

方法1:sendmail -t にヘッダーごと渡す

MTA があるなら、最も挙動が読みやすいのはヘッダーと本文を自分で組み立てて sendmail -t に渡す方法です。-t は宛先を本文中の To / Cc / Bcc ヘッダーから読み取るオプション、-oi は本文中にピリオドだけの行があってもそこで終わりと見なさないオプションです。-f で指定したアドレスはエンベロープの送信元(Return-Path)になり、エラーメールの戻り先になります。

#!/usr/bin/env bash
set -euo pipefail

to="admin@example.com"
from="server@example.com"
subject="バックアップ完了通知 $(hostname)"
log_file="/var/log/backup/latest.log"

# 日本語の件名は RFC 2047 の形式(Base64)にエンコードする
encoded_subject="=?UTF-8?B?$(printf '%s' "$subject" | base64 | tr -d '\n')?="

{
  printf 'From: %s\n' "$from"
  printf 'To: %s\n' "$to"
  printf 'Subject: %s\n' "$encoded_subject"
  printf 'Date: %s\n' "$(LC_ALL=C date -R)"
  printf 'MIME-Version: 1.0\n'
  printf 'Content-Type: text/plain; charset=UTF-8\n'
  printf 'Content-Transfer-Encoding: 8bit\n'
  printf '\n'
  tail -n 50 "$log_file"
} | /usr/sbin/sendmail -t -oi -f "$from"

ヘッダーと本文の間には空行が1行必要です。これを忘れると、本文の1行目がヘッダーとして解釈されて表示が崩れます。date -R は GNU coreutils の書式で、メールの Date ヘッダーに使える形式を出力します。

日本語の件名と本文の文字化けを防ぐ

メールのヘッダーは本来 ASCII 文字しか書けないため、日本語の件名はそのまま入れると受信側で文字化けすることがあります。上の例のように =?UTF-8?B?...?= の形式に変換しておけば、一般的なメールソフトで正しく表示されます。

ただし、エンコードした1つの塊は規格上75文字までとされています。件名が長いと上限を超えるため、多くのメールソフトは表示してくれるものの、厳密な環境では崩れる可能性があります。通知の件名は短くまとめ、長い説明は本文に回すのが無難です。本文は Content-Type で charset=UTF-8 を明示しておけば、多くの場合は 8bit のまま問題なく届きます。古い中継サーバーを経由する可能性がある場合は、本文も Base64 にして Content-Transfer-Encoding: base64 とする方法があります。

方法2:mail / mailx コマンドで手軽に送る

短い通知だけなら mail コマンドが手軽です。

echo "ディスク使用率が90%を超えました" | mail -s "disk alert" admin@example.com

注意したいのは、mail / mailx の実装がディストリビューションによって異なる点です。Debian / Ubuntu では bsd-mailx や s-nail、RHEL 系では s-nail ベースの mailx などが使われ、オプションの意味が一致しません。たとえば添付ファイルの指定に使われる -a は、s-nail では添付を意味しますが、bsd-mailx では追加ヘッダーの指定です。別のサーバーへスクリプトを移したときに「添付が付かない」「余計なヘッダーが入る」といった問題が起きやすいので、実装に依存する機能は man mail で確認してから使ってください。

添付ファイルが必要な場合は、オプションの意味が安定している mutt を使う方法もあります。

mutt -s "週次レポート" -a /tmp/report.csv -- admin@example.com < /tmp/report_body.txt

-- は「ここから先は宛先」という区切りで、-a の後に複数のファイルを並べたときにも宛先と混ざらないようにするためのものです。

方法3:msmtp で外部 SMTP を使う

MTA を常駐させずに外部の SMTP サーバーへ送りたい場合は、msmtp が扱いやすい選択肢です。sendmail 互換のインターフェースを持つため、方法1のスクリプトの最後を msmtp -t に置き換えるだけで流用できます。

# ~/.msmtprc(chmod 600 にしておく)
defaults
auth           on
tls            on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile        ~/.msmtp.log

account        notify
host           smtp.example.com
port           587
tls_starttls   on
from           server@example.com
user           server@example.com
passwordeval   "cat ~/.config/msmtp/notify.pass"

account default : notify

パスワードを設定ファイルに直接書く代わりに、passwordeval で別ファイルや鍵管理ツールから読み込むようにしておくと、設定ファイルを共有したりバックアップしたりしたときの漏えいを防ぎやすくなります。パスワードファイルも所有者だけが読める権限にしてください。Gmail や Microsoft 365 は通常のログインパスワードでの SMTP 認証を制限しており、アプリパスワードや OAuth などが必要になる場合があります。認証方式は各サービスの案内に従ってください(2026年10月現在も方針の変更が続いているため、設定前に最新の公式ドキュメントを確認してください)。

tls_trust_file のパスはディストリビューションによって異なります。RHEL 系では /etc/pki/tls/certs/ca-bundle.crt などになるため、存在するファイルを指定してください。

方法4:curl で SMTP 送信する

curl は SMTP にも対応しており、追加のパッケージを入れられない環境で役立ちます。メール本文はヘッダー込みのファイルとして用意し、--upload-file で渡します。

curl --silent --show-error \
  --url "smtp://smtp.example.com:587" --ssl-reqd \
  --mail-from "server@example.com" \
  --mail-rcpt "admin@example.com" \
  --netrc-file "$HOME/.config/curl/smtp.netrc" \
  --upload-file /tmp/mail.txt

--user 'ユーザー:パスワード' でも認証できますが、コマンドライン引数は ps で他のユーザーから見える場合があり、シェルの履歴にも残ります。認証情報は --netrc-file で権限を絞ったファイルから読むほうが安全です。--ssl-reqd を付けると、STARTTLS に失敗したときに平文で送らずエラーにします。

cron から送るときの注意点

cron で動かすスクリプトは、ログイン時とは環境変数が異なります。PATH が短く、sendmail が /usr/sbin にあると見つからないことがあるため、上の例のように /usr/sbin/sendmail とフルパスで書くか、スクリプトの冒頭で PATH を設定してください。

また、crontab に MAILTO を設定すると、ジョブの標準出力や標準エラーが cron 自身によってメールで送られます。スクリプトの中でもメールを送ると通知が二重になるので、どちらで通知するかを決めておきましょう。失敗時だけ通知したい場合は、終了コードで分岐させます。

if ! /usr/local/bin/backup.sh > /tmp/backup.out 2>&1; then
  /usr/local/bin/notify-mail.sh "バックアップ失敗" /tmp/backup.out
fi

ループの中で大量に送る作りは避けてください。短時間に多数のメールを送ると、外部 SMTP の送信上限に達したり、スパムと判定されて送信元の評判が下がったりします。複数の異常をまとめて1通にする、同じ通知は一定時間抑止する、といった工夫が有効です。

届かないときの確認手順

送信したのに届かない場合は、手前から順に確認します。

  1. スクリプトの終了コード: sendmail や msmtp が 0 以外で終わっていないかを確認します。set -e を使っていれば、失敗した時点でスクリプトが止まります。
  2. MTA のログ: Postfix なら journalctl -u postfix や /var/log/mail.log(RHEL 系では /var/log/maillog)に、配送の状態(status=sent、deferred、bounced)が記録されます。mailq で送信待ちのキューも確認できます。
  3. 外部 SMTP の応答: msmtp は logfile に、curl は --verbose を付けると SMTP のやり取りを表示します。認証エラーやポートの遮断はここで分かります。
  4. 迷惑メールフォルダと送信ドメイン認証: 受信側で迷惑メールに振り分けられていないかを確認します。送信元ドメインに SPF・DKIM・DMARC が設定されていないと、届いても迷惑メール扱いになったり、受け取りを拒否されたりします。Gmail などの大手は送信者向けのガイドラインを公開しているので、自社ドメインから送る場合は一度目を通しておくと原因を絞りやすくなります。

まとめ

  • MTA があるなら sendmail -t -oi にヘッダーごと渡す方法が最も挙動を把握しやすい
  • 日本語の件名は =?UTF-8?B?...?= 形式にエンコードし、本文は charset=UTF-8 を明示する
  • mail / mailx は実装ごとにオプションの意味が違うため、移植するときは man で確認する
  • 外部 SMTP を使うなら msmtp か curl。認証情報はコマンドラインに書かず、権限を絞ったファイルから読む
  • cron では PATH と MAILTO に注意し、通知は失敗時だけ・まとめて送る作りにする
  • 届かないときは終了コード、MTA のログ、SMTP の応答、送信ドメイン認証の順に確認する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲