You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
7.4 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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()