#!/usr/bin/env python3 """ 邮件批量通知工具 - 简易版 直接运行即可发送,无需额外参数 """ import os import re import sys import time import smtplib import logging from datetime import datetime from email.mime.text import MIMEText from email.header import Header # ===================== 配置区(按需修改) ===================== SMTP_SERVER = "smtp.ctvit.com.cn" SMTP_PORT = 25 SENDER_EMAIL = "zhangguozhong@ctvit.com.cn" SENDER_PASS = "&TBJ7p^u$HF9hp" SUBJECT = "账号及口令通知" REPLY_TO = "" # 回复地址(可选,留空则不填) DELAY = 1 # 每封间隔(秒) MAX_RETRIES = 3 # 失败重试次数 USER_FILE = "users.txt" # 用户列表文件 TEMPLATE_FILE = "" # 自定义模板文件(留空用默认模板) # ============================================================= DEFAULT_TEMPLATE = """亲爱的用户: 您好!这是您的账号配置信息: 您的邮件账号:{email} 您的初始口令:{password} 请务必妥善保管您的账号信息,不要泄露给他人。 如有疑问,请联系技术支持部门。 --- 此邮件为系统自动发送,请勿直接回复。""" # 日志 logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger("MailSender") def parse_users(file_path): users = [] with open(file_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue parts = re.split(r"[\s\t\|:;]+", line) if len(parts) < 2: continue email = parts[0].strip() password = parts[1].strip() if not email or "@" not in email: logger.warning(f"跳过无效行: {line}") continue users.append({"email": email, "password": password}) return users def build_smtp(): if SMTP_PORT == 465: server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, timeout=30) logger.info(f"📡 连接 {SMTP_SERVER}:{SMTP_PORT} (SSL)") elif SMTP_PORT == 587: server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout=30) server.ehlo() server.starttls() server.ehlo() logger.info(f"📡 连接 {SMTP_SERVER}:{SMTP_PORT} (STARTTLS)") else: server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout=30) logger.info(f"📡 连接 {SMTP_SERVER}:{SMTP_PORT} (明文)") server.login(SENDER_EMAIL, SENDER_PASS) return server def send_one(server, receiver_email, content): msg = MIMEText(content, "plain", "utf-8") msg["From"] = SENDER_EMAIL msg["To"] = receiver_email msg["Subject"] = Header(SUBJECT, "utf-8") if REPLY_TO: msg["Reply-To"] = REPLY_TO server.sendmail(SENDER_EMAIL, [receiver_email], msg.as_string()) def send_with_retry(server, receiver_email, content): for attempt in range(1, MAX_RETRIES + 1): try: send_one(server, receiver_email, content) return True, None except smtplib.SMTPServerDisconnected: if attempt < MAX_RETRIES: wait = 2 ** attempt logger.warning(f" ⚠ 连接断开,{wait}s 后重试 (第{attempt}次)") time.sleep(wait) except smtplib.SMTPResponseException as e: code = e.smtp_code if 400 <= code < 500 and attempt < MAX_RETRIES: wait = 2 ** attempt logger.warning(f" ⚠ SMTP {code},{wait}s 后重试 (第{attempt}次)") time.sleep(wait) else: return False, f"SMTP {code}: {e}" except Exception as e: return False, str(e) return False, "重试耗尽" def main(): logger.info("=" * 50) logger.info("📧 邮件批量通知工具 启动") logger.info(f" 发件人:{SENDER_EMAIL}") logger.info(f" SMTP:{SMTP_SERVER}:{SMTP_PORT}") logger.info(f" 间隔:{DELAY}s  重试:{MAX_RETRIES}次") logger.info("=" * 50) # 1. 检查文件 if not os.path.exists(USER_FILE): logger.error(f"❌ 文件不存在: {USER_FILE}") sys.exit(1) # 2. 读取模板 if TEMPLATE_FILE: with open(TEMPLATE_FILE, "r", encoding="utf-8") as f: template = f.read() logger.info(f"📝 自定义模板: {TEMPLATE_FILE}") else: template = DEFAULT_TEMPLATE logger.info("📝 默认模板") # 3. 解析用户 users = parse_users(USER_FILE) if not users: logger.error("❌ 未识别到有效用户") sys.exit(1) logger.info(f"📋 共 {len(users)} 个收件人") # 4. 测试发送(先发给自己验证) logger.info("🧪 正在测试发送(发给自己验证)...") server = None try: server = build_smtp() test_content = template.format(email=SENDER_EMAIL, password="123456") send_one(server, SENDER_EMAIL, test_content) logger.info(f"✅ 测试成功!已发送至 {SENDER_EMAIL}") except smtplib.SMTPAuthenticationError: logger.error("❌ SMTP 认证失败,请检查密码/授权码") sys.exit(1) except Exception as e: logger.error(f"❌ 测试发送失败: {e}") logger.error(" 请检查 SMTP 服务器和端口是否正确") sys.exit(1) finally: if server: try: server.quit() except Exception: pass # 5. 正式发送 total = len(users) success = 0 failed = 0 failed_list = [] print() logger.info(f"🚀 开始批量发送(共 {total} 封)") print("─" * 50) for idx, user in enumerate(users, 1): email = user["email"] content = template.format(email=email, password=user["password"]) server = None try: server = build_smtp() ok, err = send_with_retry(server, email, content) except smtplib.SMTPAuthenticationError: logger.error("❌ SMTP 认证失败") sys.exit(1) except Exception as e: ok, err = False, str(e) finally: if server: try: server.quit() except Exception: pass if ok: success += 1 logger.info(f" [{idx}/{total}] ✅ {email}") else: failed += 1 failed_list.append({"email": email, "error": err}) logger.error(f" [{idx}/{total}] ❌ {email} → {err}") if idx < total: time.sleep(DELAY) # 6. 汇总 print("═" * 50) logger.info(f"📊 发送完成") logger.info(f" 总 数:{total}") logger.info(f" 成 功:{success}") logger.info(f" 失 败:{failed}") if failed_list: print() logger.warning("📋 失败人员:") for f in failed_list: logger.warning(f" ❌ {f['email']} → {f['error']}") log_file = f"failed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" with open(log_file, "w", encoding="utf-8") as f: for fu in failed_list: f.write(f"{fu['email']}\t{fu['error']}\n") logger.info(f"📁 失败详情已保存: {log_file}") if failed == 0: logger.info("🎉 全部发送成功!") if __name__ == "__main__": main()