ラズパイ 読む時間 約4分

ラズパイからメールで知らせる:Python の標準ライブラリだけで、自分あてに通知を送る

ラズパイから、レンタルサーバーなどのメールの口座を使って、自分あてに通知のメールを送る方法です。Python の smtplib だけで動き、パスワードは設定ファイルに置きます。声や LINE の通知と組み合わせる例も紹介します。

ラズパイからメールで知らせる:Python の標準ライブラリだけで、自分あてに通知を送る

ラズパイからのお知らせを、メールでも受けられるようにします。メールは、スマホでも PC でも読めて、あとから見返したり、ほかの人に転送したりしやすいのがよいところです。声や LINE ほどすぐには気づかないので、「急ぎではないけれど、記録として残したいお知らせ」に向いています。

この記事では、Python の標準ライブラリ(smtplib)だけで、レンタルサーバーなどのメールアカウントから、自分あてにメールを送る仕組みを作ります。

この記事の前にできていること

  • ラズパイに PC から SSH で接続できる(第 3 回まで)
  • 送るためのメールアカウント(レンタルサーバーのメールなど)。SMTP のサーバーの名前・ポート・ユーザー名・パスワードが分かっていること

この記事でできるようになること

  • ラズパイから、自分あてにメールを送る
  • 声や LINE のお知らせと同じきっかけで、メールにも送る

先に知っておくこと

  • 通知用のアカウントを分けるのがおすすめ:ふだん使いのアカウントのパスワードをラズパイに置くより、通知を送るだけのアカウントを 1 つ作るほうが安全です(万一パスワードが漏れても、被害がそのアカウントだけで済みます)。
  • Gmail から送るときは「アプリ パスワード」:Gmail の SMTP を使うときは、ふだんのパスワードではなく、Google のアカウントで作るアプリ パスワードを使います(2 段階認証が必要です)。
  • 送り先は自分だけ:通知のメールを、ほかの人のアドレスに送らないようにします。自分あてなら、迷惑メールと判断されにくくもなります。

手順 A:スクリプトと設定を置く

ラズパイの ~/notify に、次の 2 つのファイルを作ります。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ラズパイから、自分あてにメールで知らせる(SMTP・Python の標準ライブラリだけ)
使い方:python3 mail_send.py "件名" "本文"
        ほかの Python からは:import mail_send; mail_send.send("件名", "本文")
設定は同じフォルダの mail_send.ini(送るメールアカウント・送り先)。
"""
import configparser
import os
import smtplib
import ssl
import sys
from email.message import EmailMessage
from email.utils import formatdate, make_msgid
HERE = os.path.dirname(os.path.abspath(__file__))
NEED = ("host", "user", "password", "from", "to")
def load():
    cfg = configparser.ConfigParser(interpolation=None)  # パスワードの % をそのまま読む
    path = os.path.join(HERE, "mail_send.ini")
    try:
        if not cfg.read(path, encoding="utf-8") or not cfg.has_section("smtp"):
            raise RuntimeError("設定ファイル mail_send.ini([smtp])がありません")
        c = cfg["smtp"]
        missing = [k for k in NEED if not c.get(k, "").strip()]
        port = c.getint("port", fallback=465)
    except configparser.Error as e:
        raise RuntimeError("mail_send.ini の書き方がちがいます(%s)" % e.__class__.__name__)
    except ValueError:
        raise RuntimeError("mail_send.ini の port が数字になっていません")
    if missing:
        raise RuntimeError("mail_send.ini に %s が入っていません" % "・".join(missing))
    return c, port
def send(subject, body):
    """送れたら True。送り先は mail_send.ini の to(自分のアドレスだけにしておく)"""
    try:
        c, port = load()
    except RuntimeError as e:
        print("送れませんでした:%s" % e, file=sys.stderr)
        return False
    msg = EmailMessage()
    msg["Subject"] = subject[:200]
    msg["From"] = c["from"]
    msg["To"] = c["to"]
    msg["Date"] = formatdate(localtime=True)
    msg["Message-ID"] = make_msgid(domain=c["from"].rpartition("@")[2] or None)
    msg.set_content(body)
    try:
        if port == 465:  # はじめから暗号化(SMTPS)
            with smtplib.SMTP_SSL(c["host"], port, context=ssl.create_default_context(), timeout=20) as s:
                s.login(c["user"], c["password"])
                s.send_message(msg)
        else:  # 587:接続してから暗号化(STARTTLS)
            with smtplib.SMTP(c["host"], port, timeout=20) as s:
                s.starttls(context=ssl.create_default_context())
                s.login(c["user"], c["password"])
                s.send_message(msg)
        return True
    except (smtplib.SMTPException, OSError) as e:
        # パスワードは表示しない
        print("送れませんでした:%s" % e.__class__.__name__, file=sys.stderr)
        return False
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(__doc__)
        sys.exit(1)
    if send(sys.argv[1], sys.argv[2]):
        print("送った")
        sys.exit(0)
    sys.exit(2)
[smtp]
host = smtp.example.com
port = 465
user = notify@example.com
password = (メールアカウントのパスワード)
from = notify@example.com
to = me@example.com
chmod 600 ~/notify/mail_send.ini

書き方と役目

  • port:465 ははじめから暗号化(SMTPS)、587 は接続してから暗号化(STARTTLS)。どちらを使うかは、メールアカウントの説明のページに書いてあります
  • interpolation=None:パスワードに % が入っていても、そのまま読む
  • パスワードは表示しない:送れなかったときに出すのは、エラーの種類の名前だけ
  • Message-ID・Date:メールの決まりの欄。これがないと迷惑メールと判断されやすい
  • 必要な値を先に確認する:host・user・password・from・to が空のときや、port が数字でないときは、送らずに理由を出して止まる
  • 送れたら「送った」:コマンドで動かしたときだけ出す。ほかの Python から send() を呼んだときは、何も出さずに True を返す

手順 B:送ってみる

cd ~/notify
python3 mail_send.py "[ラズパイ] テスト" "ラズパイからの通知のテストです。"

家で送ってみた結果

  • 送り元はレンタルサーバーのメール(465 番・はじめから暗号化)で、送り先は自分のアドレス
  • 件名「[ラズパイ] メールの通知のテスト」・本文に送った時刻を書いて、1 通送った
  • スクリプトは「送った」で終わり、自分のメールの受信箱に届いた

設定ファイルがないときや、必要な値が入っていないときは、送らずに理由を出して止まります。パスワードがまちがっているときは、「送れませんでした:SMTPAuthenticationError」のように、エラーの種類の名前だけを出して止まります。

送れませんでした:設定ファイル mail_send.ini([smtp])がありません

声や LINE と一緒に使う

売れたメールを声で知らせる仕組みで声を置いている所や、LINE で知らせる仕組みと同じように、1 行足せばメールにも届きます。

import mail_send
mail_send.send("[ラズパイ] バックアップに失敗しました", "db_backup.sh が失敗しました。backup.log を見てください。")

件名の頭を [ラズパイ] のように決めておくと、メールの振り分けの決まりで、通知だけをまとめて見られます。

まとめ

Python の標準ライブラリだけで、ラズパイから自分あてにメールで知らせる仕組みを作りました。すぐに気づく声や LINE と、あとから見返せるメールを組み合わせると、お知らせの漏れが減ります。

次に読む

この記事を書いた人

フリースタイル

Web の仕事 25 年以上。WordPress の保守・引っ越しと、PHP の業務の仕組みづくり。 プロフィール →

まずは、お気軽にご相談ください。

相談と見積りは無料です。急ぎのときは、フォームで「急ぎ」を選んでください。