パスワード認証を使用するメールの重要通知をOpenClaw経由でDiscordへ送る構成
パスワード認証を使用するメールアカウントに届いたメールをUbuntuサーバで確認し、特定のキーワードを含むメールだけをOpenClaw経由でDiscordへ通知する手順をまとめます。
Microsoft 365版ではMicrosoft Graph APIとOAuth2を使いましたが、パスワード認証を使用するメール版では一般的なIMAP受信を使います。OAuth2ではなくメールアカウントの認証情報を扱うため、パスワード管理を慎重に行う必要があります。
メールサービス
↓ IMAP over SSL/TLS
Ubuntuサーバ
↓ メール取得スクリプト
キーワード判定
↓
OpenClaw CLI
↓
Discordチャンネル通知
メールサービス側でIMAP設定を確認する
まず、パスワード認証メールの管理画面やメール設定画面で、IMAP接続に必要な情報を確認します。
- IMAPサーバ名
- IMAPポート番号
- SSL/TLSの有無
- メールアドレス
- メールパスワード
一般的には、IMAP over SSL/TLSを使う場合のポートは 993 です。ただし、実際のサーバ名や設定値は契約中の利用中のメールサービスの管理画面に表示されている内容を使います。
通知専用メールアカウントを用意する
可能であれば、普段使いの個人メールアカウントではなく、通知監視専用のメールアカウントを用意します。
監視専用アカウントにしておくと、パスワード漏えい時の影響範囲を小さくできます。また、通知対象のメールだけを転送する運用にすると、Discordへ流す情報も最小限にできます。
Ubuntu側に必要なパッケージを入れる
Ubuntu側では、PythonでIMAP接続とメール解析を行います。
sudo apt update
sudo apt install -y python3
標準ライブラリの imaplib、email、ssl を使うため、追加のPythonパッケージは必須ではありません。
Ubuntu側に設定ファイルを作成する
メールパスワードはOpenClawのワークスペースやWeb公開ディレクトリには置かず、rootだけが読める設定ファイルに保存します。以下のパスは例です。
sudo install -m 600 -o root -g root /dev/null /etc/example-password-auth-mail-watcher.env
sudo nano /etc/example-password-auth-mail-watcher.env
設定ファイルの例です。実際の値は環境に合わせて置き換えます。
IMAP_HOST="imap.example.jp"
IMAP_PORT="993"
IMAP_USER="mailbox@example.com"
IMAP_PASSWORD="dummy-mail-password"
KEYWORDS="障害|緊急|至急|アラート|エラー"
OPENCLAW_NOTIFY_TARGET="channel:123456789012345678"
OPENCLAW_NOTIFY_TARGET には、通知したいDiscordチャンネルIDを指定します。
状態保存用ディレクトリを作成する
同じメールを何度も通知しないよう、処理済みメールのUIDを保存するディレクトリを作成します。
sudo install -d -m 700 -o root -g root /var/lib/example-password-auth-mail-watcher
メール取得とキーワード判定スクリプトを作成する
IMAPでメールを取得し、件名や本文にキーワードが含まれる場合だけDiscordへ通知するスクリプトを作成します。以下のパスは例です。
sudo nano /usr/local/bin/example-password-auth-mail-watcher.py
スクリプト例です。
#!/usr/bin/env python3
import email
import imaplib
import os
import re
import ssl
import subprocess
from email.header import decode_header
from email.message import Message
ENV_FILE = "/etc/example-password-auth-mail-watcher.env"
STATE_FILE = "/var/lib/example-password-auth-mail-watcher/seen_uids.txt"
def load_env(path):
values = {}
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
values[key] = value.strip().strip('"').strip("'")
return values
def decode_mime(value):
if not value:
return ""
parts = []
for text, charset in decode_header(value):
if isinstance(text, bytes):
parts.append(text.decode(charset or "utf-8", errors="replace"))
else:
parts.append(text)
return "".join(parts)
def extract_text(message: Message):
if message.is_multipart():
chunks = []
for part in message.walk():
content_type = part.get_content_type()
disposition = str(part.get("Content-Disposition") or "")
if "attachment" in disposition.lower():
continue
if content_type == "text/plain":
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or "utf-8"
chunks.append(payload.decode(charset, errors="replace"))
return "\n".join(chunks)
payload = message.get_payload(decode=True)
if not payload:
return ""
charset = message.get_content_charset() or "utf-8"
return payload.decode(charset, errors="replace")
def load_seen_uids(path):
if not os.path.exists(path):
return set()
with open(path, "r", encoding="utf-8") as f:
return set(line.strip() for line in f if line.strip())
def save_seen_uids(path, seen):
with open(path, "w", encoding="utf-8") as f:
for uid in sorted(seen, key=lambda x: int(x) if x.isdigit() else x):
f.write(uid + "\n")
def send_discord(target, matched_lines):
message = "パスワード認証メール キーワード一致: {}件\n\n{}".format(
len(matched_lines),
"\n".join(matched_lines),
)
subprocess.run(
[
"openclaw",
"message",
"send",
"--channel",
"discord",
"--target",
target,
"--message",
message,
],
check=True,
)
def main():
env = load_env(ENV_FILE)
host = env["IMAP_HOST"]
port = int(env.get("IMAP_PORT", "993"))
user = env["IMAP_USER"]
password = env["IMAP_PASSWORD"]
keywords = env["KEYWORDS"]
notify_target = env["OPENCLAW_NOTIFY_TARGET"]
keyword_re = re.compile(keywords, re.IGNORECASE)
seen = load_seen_uids(STATE_FILE)
matched = []
context = ssl.create_default_context()
with imaplib.IMAP4_SSL(host, port, ssl_context=context) as client:
client.login(user, password)
client.select("INBOX")
status, data = client.uid("search", None, "ALL")
if status != "OK":
raise RuntimeError("IMAP search failed")
uids = data[0].decode().split()
for uid in uids[-50:]:
if uid in seen:
continue
status, msg_data = client.uid("fetch", uid, "(RFC822)")
if status != "OK":
continue
raw = msg_data[0][1]
msg = email.message_from_bytes(raw)
subject = decode_mime(msg.get("Subject"))
from_addr = decode_mime(msg.get("From"))
body = extract_text(msg)
if keyword_re.search(subject) or keyword_re.search(body):
matched.append("- From: {}\n 件名: {}".format(from_addr, subject or "(no subject)"))
seen.add(uid)
client.logout()
if matched:
send_discord(notify_target, matched)
save_seen_uids(STATE_FILE, seen)
if __name__ == "__main__":
main()
スクリプトに実行権限を付けます。
sudo chmod 700 /usr/local/bin/example-password-auth-mail-watcher.py
OpenClawからDiscord通知できるか確認する
先にOpenClaw経由でDiscordへ通知できることを確認します。チャンネルIDはダミーです。
openclaw message send \
--channel discord \
--target channel:123456789012345678 \
--message "メール通知テスト"
通知できれば、次にメール取得スクリプトを手動実行します。
sudo /usr/local/bin/example-password-auth-mail-watcher.py
systemd serviceを作成する
手動実行で問題がなければ、systemd serviceを作成します。
sudo nano /etc/systemd/system/example-password-auth-mail-watcher.service
[Unit]
Description=Watch password-auth mail and notify Discord via OpenClaw
[Service]
Type=oneshot
ExecStart=/usr/local/bin/example-password-auth-mail-watcher.py
systemd timerを作成する
5分ごとに実行するtimerを作成します。
sudo nano /etc/systemd/system/example-password-auth-mail-watcher.timer
[Unit]
Description=Run password-auth mail watcher periodically
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.target
timerを有効化します。
sudo systemctl daemon-reload
sudo systemctl enable --now example-password-auth-mail-watcher.timer
動作確認を行う
timerの状態と実行ログを確認します。
systemctl list-timers example-password-auth-mail-watcher.timer
journalctl -u example-password-auth-mail-watcher.service -n 50 --no-pager
テスト用にキーワードを含むメールを対象メールボックスへ送り、Discordに通知されることを確認します。
通知内容は最小限にする
Discordへ通知する内容は、必要最小限にするのが安全です。本文全文や添付ファイルをそのまま流すと、情報漏えいのリスクが上がります。
最初は次の程度に抑えるのがよいです。
- 検出件数
- 送信者
- 件名
- 一致したキーワード
運用時の注意点
- メールパスワードはOpenClawワークスペースに置かない
- 設定ファイルはrootのみ読める権限にする
- IMAPはSSL/TLSで接続する
- 可能なら通知専用メールアカウントを使う
- Discordには本文全文や添付ファイルを流さない
- ログにメールパスワードや本文を出さない
- 同じメールを何度も通知しないようUIDを保存する
- 最初は5分間隔程度で運用する
Microsoft 365版との違い
Microsoft 365版では、Microsoft Graph APIとOAuth2を使ってメールを取得します。一方、パスワード認証を使用するメール版ではIMAP over SSL/TLSを使います。
パスワード認証を使用するメール版は構成がシンプルですが、メールパスワードをサーバに保存する必要があります。そのため、専用アカウントを使い、設定ファイルの権限を厳しく制限することが重要です。
まとめ
パスワード認証を使用するメールの重要通知をDiscordへ通知するには、UbuntuサーバからIMAP over SSL/TLSでメールを取得し、件名や本文に特定キーワードが含まれている場合だけOpenClaw CLIでDiscordへ通知する構成が実用的です。
Microsoft 365版より設定は簡単ですが、OAuth2ではなくメールパスワードを扱うため、パスワード管理と通知内容の最小化が重要です。