とあるバッチ処理がエラーを吐いたときにメールを送る必要がありました。 メール送信の処理をPython2.7で書いたのでメモです。
最終的に以下のようにcronを実行してエラーがあればメールを送信する仕組みです。
# clontab -l
0 0 * * * sh /path/batch.sh 2>&1 1>/dev/null | python /path/mail.py "バッチ処理[Subject]"
説明が長いのでコードだけ知りたい方は一番下のコードを見て下さい。
環境
- CentOS 7.4
- postfix 2.10.1
- Python 2.7.5
汎用性を持たせるためLinuxで標準のPython 2.7を利用しました。 postfixは動作確認済みです。
Red Hat Enterprise Linux 8からはPython 3.6が標準で利用できるみたいです(RedHat)。
smtplibを使ってメールを送信する
SMTPクライアントのsmtplibモジュールを使ってメールを送信します。 メールを送る簡易的なコードを載せます。
import smtplib
from email.mime.text import MIMEText
_from = 'from@example.jp'
_to = 'to@someone.com'
# Create a text/plain message
msg = MIMEText('Hello World')
msg['Subject'] = 'subject'
msg['From'] = _from
msg['To'] = _to
# メール送信
s = smtplib.SMTP('localhost')
s.sendmail(_from, [_to], msg.as_string())
s.quit()
MIMETextでメールを整形し、smtplibでメールサーバのSMTPコネクションを作成してメールを送信している。
MIMETextの補足
MIMEText(_text[, _subtype[, _charset]])
となっており、メールのContent-Type
を指定できます。
例としてMIMEText(text, 'html', 'iso-2022-jp')
と引数を渡すとContent-Type: text/html; charset="iso-2022-jp"
となります。
デフォルトでは、_subtype='plain'
と_charset='us-ascii'
です。
headerを付けたい場合はmsg['X-Test'] = 'Problem'
とMIMETextオブジェクトに追加する。
Python2.7 doc email.mime:ゼロからのメールと MIME オブジェクトの作成
smtplibの補足
メールサーバを指定したいときはsmtplib.SMTP([host[, port[, local_hostname[, timeout]]]])
の引数を変える。host
はメールサーバのホストで、local_hostname
を指定するとHELO/EHLO コマンドでのローカルホストの FQDN として使われるそう。タイムアウト時間の単位は秒。
コネクションが失敗した場合はSMTPConnectError
が返る。
ドキュメントに幾つかの使用例が載っています。
バッチのエラー内容をメールで送信する
クーロンに戻って、バッチのエラー内容をメールで送信するパイプについて説明します。
# clontab -l
0 0 * * * sh /path/batch.sh 2>&1 1>/dev/null | python /path/mail.py "バッチ処理[Subject]"
出力の制御
2>&1 1>/dev/null
の部分はスクリプトの標準出力と標準エラー出力の制御をしている。
Unixには、次の3つの入出力があり、それぞれ番号が振られています。
- 0: 標準入力
- 1: 標準出力
- 2: 標準エラー出力
> /dev/null
は出力結果を捨てる処理で、2>&1
は標準エラー出力を標準出力にマージするという意味です。
つまり、2>&1 1>/dev/null
で標準出力を捨て標準エラー出力を標準出力としてパイプの次のコマンドに渡している。
echo "stdout" >&1
echo "stderr" >&2
batch.sh
はこうしておくと標準出力、標準エラー出力のテストが楽。
いい加減覚えよう。 command > /dev/null 2>&1
の意味 - Qiita
標準出力を受け取ってメール送信するpythonのコード
標準エラー出力を標準出力として受け取って空でなければ内容をメールで送信するコードです。
import smtplib
import sys
from email.mime.text import MIMEText
_from = 'from@example.jp'
_to = 'to@someone.com'
# 引数受け取り
args = sys.argv
subject = args[1]
try:
# 標準入力受け取り
text = sys.stdin.read()
except IndexError:
text = ''
if not text:
# 標準入力がエラーだったら終了
print 'no message'
sys.exit()
# ontent-Type: text/plain; charset="iso-2022-jp"
msg = MIMEText(text, _subtype='plain', _charset='iso-2022-jp')
msg['Subject'] = subject
msg['From'] = _from
msg['To'] = _to
# headers
msg['X-Alert'] = 'Problem'
# print msg.as_string()
# メール送信
s = smtplib.SMTP('localhost')
s.sendmail(_from, [_to], msg.as_string())
s.quit()
書いていて振り返るとsys.stdin
じゃなくてsys.stderr
を使えばコマンドもスッキリするけど、クーロン見て標準出力捨ててるなと一目で分かるので良しとする。