schedule2020-02-26

Python2.7 smtplibで保守のメールを送る

とあるバッチ処理がエラーを吐いたときにメールを送る必要がありました。 メール送信の処理を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を使えばコマンドもスッキリするけど、クーロン見て標準出力捨ててるなと一目で分かるので良しとする。

参考