#!/usr/bin/env python3 """ 邮件发送脚本 支持纯文字/HTML 邮件、抄送/密送、附件和批量发送 """ import smtplib import os import sys import json from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.utils import formatdate, make_msgid from typing import List, Optional # 配置文件路径 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) CONFIG_FILE = os.path.join(SCRIPT_DIR, "smtp_config.json") def load_config() -> dict: """加载 SMTP 配置""" if not os.path.exists(CONFIG_FILE): return {"accounts": []} with open(CONFIG_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_config(config: dict): """保存 SMTP 配置""" with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) def setup_account( name: str, smtp_server: str, smtp_port: int, email: str, password: str, use_tls: bool = True ): """配置 SMTP 账号""" config = load_config() # 检查是否已存在同名配置 for i, acc in enumerate(config["accounts"]): if acc["name"] == name: print(f"配置 '{name}' 已存在,正在更新...") config["accounts"][i] = { "name": name, "smtp_server": smtp_server, "smtp_port": smtp_port, "email": email, "password": password, # 明文存储,仅用于本地 "use_tls": use_tls } save_config(config) return # 添加新配置 config["accounts"].append({ "name": name, "smtp_server": smtp_server, "smtp_port": smtp_port, "email": email, "password": password, "use_tls": use_tls }) save_config(config) print(f"✅ 配置 '{name}' 已保存") def list_accounts(): """列出所有 SMTP 配置""" config = load_config() if not config["accounts"]: print("暂无 SMTP 配置") return print("\n=== SMTP 配置列表 ===") for i, acc in enumerate(config["accounts"], 1): print(f"\n{i}. {acc['name']}") print(f" 服务器: {acc['smtp_server']}:{acc['smtp_port']}") print(f" 邮箱: {acc['email']}") print(f" TLS: {'是' if acc['use_tls'] else '否'}") def get_account(name: Optional[str] = None) -> Optional[dict]: """获取 SMTP 配置""" config = load_config() if not config["accounts"]: return None if name is None: return config["accounts"][0] for acc in config["accounts"]: if acc["name"] == name: return acc return None def send_email( to: str, subject: str, body: str, account_name: Optional[str] = None, html: bool = False, cc: Optional[str] = None, bcc: Optional[str] = None, from_name: Optional[str] = None, attachments: Optional[List[str]] = None, verbose: bool = False ) -> bool: """ 发送邮件 参数: to: 收件人邮箱地址 subject: 邮件主题 body: 邮件内容 account_name: SMTP 配置名称(可选,默认使用第一个) html: 是否为 HTML 邮件(默认 False) cc: 抄送邮箱地址,多个用逗号分隔(可选) bcc: 密送邮箱地址,多个用逗号分隔(可选) from_name: 发件人显示名称(可选) attachments: 附件文件路径列表(可选) verbose: 是否显示详细日志(默认 False) 返回: bool: 发送成功返回 True,失败返回 False """ try: # 获取 SMTP 配置 account = get_account(account_name) if account is None: print("❌ 错误:未找到 SMTP 配置,请先运行配置") return False # 创建邮件 if attachments: msg = MIMEMultipart() msg.attach(MIMEText(body, "html" if html else "plain", "utf-8")) else: msg = MIMEText(body, "html" if html else "plain", "utf-8") # 设置邮件头 msg["Subject"] = subject msg["From"] = f"{from_name} <{account['email']}>" if from_name else account["email"] msg["To"] = to msg["Date"] = formatdate(localtime=True) msg["Message-Id"] = make_msgid(domain=account["email"].split("@")[1]) if cc: msg["Cc"] = cc # 添加附件 if attachments: for file_path in attachments: if not os.path.exists(file_path): print(f"⚠️ 附件不存在,已跳过: {file_path}") continue with open(file_path, "rb") as f: part = MIMEApplication(f.read()) filename = os.path.basename(file_path) part.add_header( "Content-Disposition", "attachment", filename=filename ) msg.attach(part) if verbose: print(f"📎 已添加附件: {filename}") # 连接 SMTP 服务器 if verbose: print(f"📡 正在连接 {account['smtp_server']}:{account['smtp_port']}...") if account["use_tls"]: server = smtplib.SMTP(account["smtp_server"], account["smtp_port"]) server.starttls() else: server = smtplib.SMTP(account["smtp_server"], account["smtp_port"]) # 登录 if verbose: print(f"🔑 正在登录 {account['email']}...") server.login(account["email"], account["password"]) # 准备收件人列表 recipients = [to] if cc: recipients.extend([addr.strip() for addr in cc.split(",")]) if bcc: recipients.extend([addr.strip() for addr in bcc.split(",")]) # 发送邮件 if verbose: print(f"📤 正在发送邮件...") server.sendmail(account["email"], recipients, msg.as_string()) server.quit() print(f"✅ 邮件发送成功") print(f" 收件人: {to}") if cc: print(f" 抄送: {cc}") if attachments: valid_attachments = [a for a in attachments if os.path.exists(a)] print(f" 附件: {len(valid_attachments)} 个") return True except Exception as e: print(f"❌ 邮件发送失败: {e}") return False def main(): """命令行入口""" import argparse parser = argparse.ArgumentParser(description="邮件发送工具") subparsers = parser.add_subparsers(dest="command", help="可用命令") # 配置命令 config_parser = subparsers.add_parser("config", help="配置 SMTP") config_parser.add_argument("name", help="配置名称") config_parser.add_argument("--server", required=True, help="SMTP 服务器地址") config_parser.add_argument("--port", type=int, required=True, help="SMTP 端口") config_parser.add_argument("--email", required=True, help="邮箱地址") config_parser.add_argument("--password", required=True, help="邮箱密码") config_parser.add_argument("--no-tls", action="store_true", help="不使用 TLS(默认使用)") # 列出配置命令 subparsers.add_parser("list", help="列出所有配置") # 发送命令 send_parser = subparsers.add_parser("send", help="发送邮件") send_parser.add_argument("--to", "-t", required=True, help="收件人邮箱") send_parser.add_argument("--subject", "-s", required=True, help="邮件主题") send_parser.add_argument("--body", "-b", required=True, help="邮件内容") send_parser.add_argument("--account", "-a", help="使用指定配置") send_parser.add_argument("--html", action="store_true", help="HTML 格式") send_parser.add_argument("--cc", help="抄送邮箱(多个用逗号分隔)") send_parser.add_argument("--bcc", help="密送邮箱(多个用逗号分隔)") send_parser.add_argument("--from-name", help="发件人显示名称") send_parser.add_argument("--attach", "-f", action="append", help="附件文件路径(可多次使用)") send_parser.add_argument("-v", "--verbose", action="store_true", help="显示详细日志") args = parser.parse_args() if args.command == "config": setup_account( name=args.name, smtp_server=args.server, smtp_port=args.port, email=args.email, password=args.password, use_tls=not args.no_tls ) elif args.command == "list": list_accounts() elif args.command == "send": success = send_email( to=args.to, subject=args.subject, body=args.body, account_name=args.account, html=args.html, cc=args.cc, bcc=args.bcc, from_name=args.from_name, attachments=args.attach, verbose=args.verbose ) sys.exit(0 if success else 1) else: parser.print_help() if __name__ == "__main__": main()