#!/usr/bin/env python3 """Check IMAP inbox for bounce-back / delivery-failure emails and alert if found. Silent (exit 0 with no output) when nothing to report. Outputs summary when bounces are found. """ import imaplib import email from email.utils import parsedate_to_datetime import os import sys from datetime import datetime, timedelta, timezone HOST = "mail.germainebrown.com" PORT = 993 ACCOUNTS = [ {"user": "g@germainebrown.com", "pw_file": "/root/.config/himalaya/g-germainebrown.pass", "label": "Germaine"}, {"user": "shonuff@germainebrown.com", "pw": "Catches.bullets1985", "label": "Sho'Nuff"}, ] CHECK_HOURS = 24 BOUNCE_KEYWORDS = [ "mail delivery failed", "delivery status notification", "undelivered", "returned mail", "mail delivery failure", "non-delivery", "delivery failed", "undeliverable", "delivery report", "returned to sender", "message bounced", ] BOUNCE_SENDERS = [ "mailer-daemon", "postmaster", "mail delivery system", ] def safe_str(val): if val is None: return "" if isinstance(val, str): return val try: return str(val) except Exception: return "" def is_bounce(msg): subject = safe_str(msg.get("Subject", "")).lower() sender = safe_str(msg.get("From", "")).lower() for kw in BOUNCE_KEYWORDS: if kw in subject: return True for s in BOUNCE_SENDERS: if s in sender: return True if msg.get_content_type() == "text/plain": payload = msg.get_payload(decode=True) if payload: body = safe_str(payload).lower() if "permanent error" in body or "could not be delivered" in body: return True return False def main(): all_bounces = [] since = (datetime.now(timezone.utc) - timedelta(hours=CHECK_HOURS)).strftime("%d-%b-%Y") for acct in ACCOUNTS: try: if "pw" in acct: pw = acct["pw"] else: pw = open(acct["pw_file"]).read().strip() except FileNotFoundError: continue try: conn = imaplib.IMAP4_SSL(HOST, PORT) conn.login(acct["user"], pw) conn.select("INBOX") except Exception as e: print(f"ERROR: {acct['label']} IMAP failed: {e}") continue status, data = conn.search(None, f'(SINCE {since})') if status != "OK": conn.logout() continue for num in data[0].split(): status, msg_data = conn.fetch(num, "(RFC822)") if status != "OK": continue raw = email.message_from_bytes(msg_data[0][1]) if is_bounce(raw): all_bounces.append({ "account": acct["label"], "subject": raw.get("Subject", "(no subject)"), "from": raw.get("From", "(unknown)"), "date": raw.get("Date", ""), }) conn.logout() if not all_bounces: return print(f"📬 {len(all_bounces)} bounce-back(s) detected in the last {CHECK_HOURS}h:\n") for b in all_bounces: print(f" [{b['account']}] {b['subject']}") print(f" From: {b['from']}") print(f" Date: {b['date']}") print() if __name__ == "__main__": main()