#!/usr/bin/env python3 """ Send a reply email via SMTP (e.g. Proton Mail Bridge). Usage: ./gitea-issues/mail-send-reply.sh --to addr@example.com --subject "..." --body "..." [--in-reply-to "" [--references ""]] Or: echo "body" | ./gitea-issues/mail-send-reply.sh --to addr@example.com --subject "..." [--in-reply-to ""] """ from __future__ import annotations import argparse import os import smtplib import sys from email.mime.text import MIMEText from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from mail_common import load_smtp_config, repo_root, imap_ssl_context DEFAULT_SIGNATURE = """-- Support IA du projet Lecoffre.io ai.support.lecoffreio@4nkweb.com""" # Patterns that indicate the body contains a citation of the received message (forbidden). CITATION_PATTERNS = ( "From:", "Message-ID:", "Message-ID :", " wrote:", " a écrit", ) CITATION_LINE_START = (">",) # Quoted line start def body_contains_citation(body: str) -> bool: """Return True if body looks like it contains the received message (citation).""" if not body or not body.strip(): return False lines = body.strip().splitlines() for line in lines: stripped = line.strip() if not stripped: continue for pat in CITATION_PATTERNS: if pat in line: return True for start in CITATION_LINE_START: if stripped.startswith(start): return True return False def get_reply_signature() -> str: sig = os.environ.get("MAIL_REPLY_SIGNATURE", "").strip() if sig: return "\n\n" + sig.replace("\\n", "\n") return "\n\n" + DEFAULT_SIGNATURE def main() -> None: ap = argparse.ArgumentParser(description="Send reply email via Bridge SMTP") ap.add_argument("--to", required=True, help="To address") ap.add_argument("--subject", required=True, help="Subject") ap.add_argument("--body", default="", help="Body (or use stdin)") ap.add_argument("--in-reply-to", default="", help="Message-ID of the message we reply to") ap.add_argument("--references", default="", help="References header for threading") args = ap.parse_args() cfg = load_smtp_config() if not cfg["user"] or not cfg["password"]: root = repo_root() env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" print("[gitea-issues] ERROR: SMTP_USER and SMTP_PASSWORD required.", file=sys.stderr) print(f"[gitea-issues] Set env or create {env_path}", file=sys.stderr) sys.exit(1) body = args.body if not body and not sys.stdin.isatty(): body = sys.stdin.read() body = body.rstrip() if body_contains_citation(body): print( "[gitea-issues] ERROR: Body must not contain the received message (no citation, no From:, Message-ID, wrote:, etc.). Send only your reply text.", file=sys.stderr, ) sys.exit(1) body = (body + get_reply_signature()).strip() msg = MIMEText(body, "plain", "utf-8") msg["Subject"] = args.subject msg["From"] = cfg["user"] msg["To"] = args.to if args.in_reply_to: msg["In-Reply-To"] = args.in_reply_to if args.references: msg["References"] = args.references with smtplib.SMTP(cfg["host"], int(cfg["port"])) as smtp: if cfg["use_starttls"]: smtp.starttls(context=imap_ssl_context(cfg.get("ssl_verify", True))) smtp.login(cfg["user"], cfg["password"]) smtp.sendmail(cfg["user"], [args.to], msg.as_string()) print("[gitea-issues] Reply sent.") if __name__ == "__main__": main()