Python编写邮件自动发送工具的完整指南

Written by

in

文章目录
  • 通过本教程,我们学习了如何使用Python的smtplib库开发一个功能完整的邮件自动发送工具。主要涵盖了以下知识点: SMTP协议基础知识 使用smtplib连接邮件服务器 创建和发送不同类型的邮件(文本、HTML、附件) 批量发送个性化邮件 异常处理和日志记录 从配置文件加载设置 这个邮件发送工具可以应用于多种场景,如系统通知、营销邮件、报表自动发送等。通过进一步扩展,还可以实现更复杂的功能,满足不同的业务需求。 以上就是Python编写邮件自动发送工具的完整指南的详细内容,更多关于Python邮件自动发送的资料请关注风君子博客其它相关文章! 您可能感兴趣的文章: python使用QQ邮箱实现自动发送邮件 Python实现自动化邮件发送过程详解 Python实战之自动发送邮件的实现 Python自动化办公之定时发送邮件的实现 懒人必备Python代码之自动发送邮件 Python利用脚本实现自动发送电子邮件
  • 目录
    • 代码实现与知识点解析
      • 1. 导入必要的库
      • 2. 配置邮件服务器参数
      • 3. 创建邮件发送类
      • 4. 实现邮件发送功能
      • 5. 实现视图
      • 6.总代码
      • 7.效果图
    • 总结

      自动化邮件发送是一个非常实用的功能。无论是系统通知、营销邮件、还是日常工作报告,Python的smtplib库都能帮助我们轻松实现这些功能。本教程将详细介绍如何使用Python的smtplib库开发一个功能完整的邮件自动发送工具。

      # 导入tkinter模块,用于创建图形界面
      import tkinter as tk
      # 导入ttk模块,用于创建标签页控件
      from tkinter import ttk, filedialog, messagebox, scrolledtext
      # 导入os模块,用于文件操作
      import os
      # 导入csv模块,用于读取CSV文件
      import csv
      # 导入datetime模块,用于获取当前日期和时间
      from datetime import datetime
      # 导入logging模块,用于日志记录
      import logging
      # 导入sys模块,用于系统操作
      import sys
      

      SMTP(Simple Mail Transfer Protocol)是一种简单的邮件传输协议,用于在服务器之间发送电子邮件。在Python中,我们可以使用smtplib库来实现SMTP客户端功能,与邮件服务器进行通信。这里我选的是QQ邮箱作为服务端,其配置如下:

      找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,开启服务并获取授权码

      将配置内容写在一个config.py文件中

      MAIL_SERVER = 'smtp.qq.com' # 邮件服务器地址
      MAIL_PORT = 465 # 邮件服务器端口
      MAIL_USE_TLS = False # 是否使用TLS
      MAIL_USERNAME = '******@qq.com' # 邮件服务器用户名
      MAIL_PASSWORD = 'ydevxpkfezjyaddd' # 邮件服务器密码
      MAIL_DEFAULT_SENDER = '*******@qq.com' # 邮件服务器默认发件人
      

      注意:授权码不是密码

      知识点:

      • SMTP服务器地址和端口:不同邮箱服务商有不同的服务器地址和端口
      • TLS加密:保护邮件传输安全
      • 应用专用密码:许多邮箱服务商(如QQ邮箱)要求使用应用专用密码而非登录密码

      • 日志
      • 发送邮件(单个收件人)
      • 发送邮件(批量)
      def create_email_sender(self):
          """创建临时配置对象"""
          # 创建一个临时的配置对象
          class TempConfig:
              def __init__(self, app):
                  self.MAIL_SERVER = app.server_var.get()
                  self.MAIL_PORT = app.port_var.get()
                  self.MAIL_USE_TLS = app.use_tls_var.get()
                  self.MAIL_USERNAME = app.username_var.get()
                  self.MAIL_PASSWORD = app.password_var.get()
                  self.MAIL_DEFAULT_SENDER = app.sender_var.get()
      
      
          class CustomEmailSender():
              def __init__(self, config):
                  self.server = config.MAIL_SERVER
                  self.port = config.MAIL_PORT
                  self.use_tls = config.MAIL_USE_TLS
                  self.username = config.MAIL_USERNAME
                  self.password = config.MAIL_PASSWORD
                  self.default_sender = config.MAIL_DEFAULT_SENDER
      
                  # 设置日志
                  self._setup_logging()
              def _setup_logging(self):
                  """设置日志记录"""
                  log_dir = "logs"
                  # 如果日志目录不存在,则创建日志目录
                  if not os.path.exists(log_dir):
                      os.makedirs(log_dir)
                  # 创建日志文件
                  log_file = os.path.join(log_dir, f"email_sender_{datetime.now().strftime('%Y%m%d')}.log")
                  # 配置日志记录
                  logging.basicConfig(
                      # 设置日志级别
                      level=logging.INFO,
                      # 设置日志格式
                      format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                      # 设置日志处理器
                      handlers=[
                          # 设置日志文件处理器
                          logging.FileHandler(log_file),
                          # 设置日志控制台处理器
                          logging.StreamHandler(sys.stdout)
                      ]
                  )
                  # 获取日志记录器
                  self.logger = logging.getLogger("EmailSender")  
              def send_email(self, to_addrs, subject, body, attachments=None, cc=None, bcc=None, html=False):
                  """发送邮件(单个收件人)"""
                  import smtplib
                  from email.mime.text import MIMEText
                  from email.mime.multipart import MIMEMultipart
                  from email.mime.application import MIMEApplication
      
                  # 转换地址格式
                  if isinstance(to_addrs, str):
                      to_addrs = [to_addrs]
                  # 如果抄送地址为字符串,则转换为列表
                  if isinstance(cc, str):
                      cc = [cc]
                  # 如果抄送地址为None,则设置为空列表
                  elif cc is None:
                      cc = []        
                  if isinstance(bcc, str):
                      bcc = [bcc]
                  elif bcc is None:
                      bcc = []
      
                  # 创建邮件对象
                  msg = MIMEMultipart()
                  msg['From'] = self.default_sender
                  msg['To'] = ', '.join(to_addrs)
                  msg['Subject'] = subject
      
                  if cc:
                      msg['Cc'] = ', '.join(cc)
      
                  # 添加邮件正文
                  if html:
                      msg.attach(MIMEText(body, 'html', 'utf-8'))
                  else:
                      msg.attach(MIMEText(body, 'plain', 'utf-8'))
      
                  # 添加附件
                  if attachments:
                      for file_path in attachments:
                          if os.path.isfile(file_path):
                              with open(file_path, 'rb') as f:
                                  attachment = MIMEApplication(f.read())
                                  attachment.add_header(
                                      'Content-Disposition', 
                                      'attachment', 
                                      filename=os.path.basename(file_path)
                                  )
                                  msg.attach(attachment)
                          else:
                              self.logger.warning(f"附件不存在: {file_path}")
      
                  # 所有收件人列表(包括抄送和密送)
                  all_recipients = to_addrs + cc + bcc
      
                  try:
                      # 连接到SMTP服务器 - 修复连接问题
                      if self.port == 465:
                          # 使用SSL
                          server = smtplib.SMTP_SSL(self.server, self.port, timeout=30)
                      else:
                          # 普通连接
                          server = smtplib.SMTP(self.server, self.port, timeout=30)
      
                          # 如果使用TLS
                          if self.use_tls:
                              server.starttls()
      
                      # 登录
                      server.login(self.username, self.password)
      
                      # 发送邮件
                      server.sendmail(self.default_sender, all_recipients, msg.as_string())
      
                      # 关闭连接
                      server.quit()
      
                      self.logger.info(f"邮件已成功发送给: {', '.join(to_addrs)}")
                      return True
      
                  except Exception as e:
                      self.logger.error(f"发送邮件失败: {str(e)}")
                      return False
      
              def send_bulk_emails(self, recipients_data, subject_template, body_template, attachments=None, html=False):
                  """批量发送邮件"""
                  # 成功发送的邮件数量
                  success_count = 0
                  # 遍历收件人数据
                  for recipient_data in recipients_data:
                      # 获取收件人邮箱地址
                      to_addr = recipient_data.get('email')
                      # 如果收件人邮箱地址为空,则跳过此条记录
                      if not to_addr:
                          self.logger.warning("缺少收件人邮箱地址,跳过此条记录")
                          continue
      
                      # 替换模板变量
                      subject = subject_template
                      body = body_template
                      # 遍历收件人数据
                      for key, value in recipient_data.items():
                          # 如果key不是邮箱地址,则替换模板变量
                          if key != 'email':
                              placeholder = f"{{{key}}}"
                              subject = subject.replace(placeholder, str(value))
                              body = body.replace(placeholder, str(value))
      
                      # 发送邮件
                      if self.send_email(to_addr, subject, body, attachments=attachments, html=html):
                          success_count += 1
      
                  # 返回成功发送的邮件数量
                  return success_count
      
          # 创建并返回自定义CustomEmailSender实例
          config = TempConfig(self)
          return CustomEmailSender(config)

      简单邮件发送

      def send_simple_email(self):
          """发送简单邮件"""
          try:
              # 使用当前配置创建EmailSender实例
              sender = self.create_email_sender()
              # 获取收件人
              to_addr = self.simple_to_var.get()
              # 获取主题
              subject = self.simple_subject_var.get()
              # 获取正文
              body = self.simple_body_text.get(1.0, tk.END)
              # 如果收件人、主题和正文为空,则提示错误
              if not to_addr or not subject or not body.strip():
                  messagebox.showerror("错误", "收件人、主题和正文不能为空")
                  return
      
              self.status_var.set("正在发送邮件...")
              self.update_idletasks()
      
              if sender.send_email(to_addr, subject, body):
                  messagebox.showinfo("成功", "邮件发送成功!")
                  self.status_var.set("邮件发送成功")
              else:
                  messagebox.showerror("错误", "邮件发送失败,请查看日志获取详细信息。")
                  self.status_var.set("邮件发送失败")
          except Exception as e:
              messagebox.showerror("错误", f"发送邮件时出错: {str(e)}")
              self.status_var.set(f"发送邮件时出错: {str(e)}")
      

      HTML邮件

      def send_html_email(self):
          """发送HTML邮件"""
          try:
              # 使用当前配置创建EmailSender实例
              sender = self.create_email_sender()
      
              to_addr = self.html_to_var.get()
              subject = self.html_subject_var.get()
              body = self.html_body_text.get(1.0, tk.END)
      
              if not to_addr or not subject or not body.strip():
                  messagebox.showerror("错误", "收件人、主题和正文不能为空")
                  return
      
              self.status_var.set("正在发送HTML邮件...")
              self.update_idletasks()
      
              if sender.send_email(to_addr, subject, body, html=True):
                  messagebox.showinfo("成功", "HTML邮件发送成功!")
                  self.status_var.set("HTML邮件发送成功")
              else:
                  messagebox.showerror("错误", "邮件发送失败,请查看日志获取详细信息。")
                  self.status_var.set("邮件发送失败")
          except Exception as e:
              messagebox.showerror("错误", f"发送邮件时出错: {str(e)}")
              self.status_var.set(f"发送邮件时出错: {str(e)}")
      

      带附件的邮件

      1.删除附件和发送附件

      def add_attachment(self):
          """添加附件"""
          files = filedialog.askopenfilenames(title="选择附件")
          for file in files:
              self.attach_files_listbox.insert(tk.END, file)
      
      def remove_attachment(self):
          """删除选中的附件"""
          selected = self.attach_files_listbox.curselection()
          if selected:
              for index in reversed(selected):
                  self.attach_files_listbox.delete(index)
      

      2.发送

      def send_attachment_email(self):
          """发送带附件的邮件"""
          try:
              # 使用当前配置创建EmailSender实例
              sender = self.create_email_sender()
      
              to_addr = self.attach_to_var.get()
              subject = self.attach_subject_var.get()
              body = self.attach_body_text.get(1.0, tk.END)
      
              if not to_addr or not subject or not body.strip():
                  messagebox.showerror("错误", "收件人、主题和正文不能为空")
                  return
      
              attachments = list(self.attach_files_listbox.get(0, tk.END))
      
              self.status_var.set("正在发送带附件的邮件...")
              self.update_idletasks()
      
              if sender.send_email(to_addr, subject, body, attachments=attachments):
                  messagebox.showinfo("成功", "带附件的邮件发送成功!")
                  self.status_var.set("带附件的邮件发送成功")
              else:
                  messagebox.showerror("错误", "邮件发送失败,请查看日志获取详细信息。")
                  self.status_var.set("邮件发送失败")
          except Exception as e:
              messagebox.showerror("错误", f"发送邮件时出错: {str(e)}")
              self.status_var.set(f"发送邮件时出错: {str(e)}")
      

      批量发送

      1.添加CSV文件

      def browse_csv(self):
          """浏览CSV文件"""
          file = filedialog.askopenfilename(title="选择CSV文件", filetypes=[("CSV文件", "*.csv")])
          if file:
              self.bulk_csv_var.set(file)
      

      2.发送

      def send_bulk_emails(self):
          """批量发送邮件"""
          try:
              # 使用当前配置创建EmailSender实例
              sender = self.create_email_sender()
              # 获取批量发送邮件的配置
              csv_file = self.bulk_csv_var.get()
              # 获取主题模板
              subject_template = self.bulk_subject_var.get()
              # 获取正文模板
              body_template = self.bulk_body_text.get(1.0, tk.END)
              # 获取是否为HTML格式
              html = self.bulk_html_var.get()
              # 如果CSV文件、主题模板和正文模板为空,则提示错误
              if not csv_file or not subject_template or not body_template.strip():
                  messagebox.showerror("错误", "CSV文件、主题模板和正文模板不能为空")
                  return
              # 如果CSV文件不存在,则提示错误
              if not os.path.isfile(csv_file):
                  messagebox.showerror("错误", f"文件不存在: {csv_file}")
                  return
      
              # 读取CSV文件
              recipients = []
              with open(csv_file, 'r', encoding='utf-8') as f:
                  reader = csv.DictReader(f)
                  for row in reader:
                      recipients.append(row)
              # 如果CSV文件为空或格式不正确,则提示错误
              if not recipients:
                  messagebox.showerror("错误", "CSV文件为空或格式不正确。")
                  return
              # 如果CSV文件为空或格式不正确,则提示错误
              self.status_var.set("正在批量发送邮件...")
              self.update_idletasks()
      
              # 批量发送邮件
              success_count = sender.send_bulk_emails(recipients, subject_template, body_template, html=html)
      
              messagebox.showinfo("成功", f"批量发送完成,成功发送 {success_count}/{len(recipients)} 封邮件。")
              self.status_var.set(f"批量发送完成,成功: {success_count}/{len(recipients)}")
      
      ​​​​​​​    except Exception as e:
              messagebox.showerror("错误", f"批量发送邮件时出错: {str(e)}")
              self.status_var.set(f"批量发送邮件时出错: {str(e)}")

      配置

      1.加载配置

      def load_config(self):
          """加载配置"""
          try:
              # 从config.py文件中导入配置
              from config import (
                  MAIL_SERVER, MAIL_PORT, MAIL_USE_TLS,
                  MAIL_USERNAME, MAIL_PASSWORD, MAIL_DEFAULT_SENDER
              )
              # 将配置数据转换为字典
              config_data = {
                  'server': MAIL_SERVER,
                  'port': MAIL_PORT,
                  'use_tls': MAIL_USE_TLS,
                  'username': MAIL_USERNAME,
                  'password': MAIL_PASSWORD,
                  'default_sender': MAIL_DEFAULT_SENDER
              }
              # 返回配置数据(字典格式)
              return config_data
          except ImportError:
              # 如果无法加载配置文件,则返回默认配置
              messagebox.showerror("错误", "无法加载配置文件,请检查config.py是否存在")
              return {
                  'server': 'smtp.qq.com',
                  'port': 465,
                  'use_tls': False,
                  'username': '2949666522@qq.com',
                  'password': 'ydevxpkfezjydddd',
                  'default_sender': '2949666522@qq.com'
              }
      

      2.保存配置

      def save_config(self):
          """修改配置文件,保存配置到config.py文件"""
          try:
              # 打开config.py文件,准备写入配置信息
              with open('config.py', 'w', encoding='utf-8') as f:
                  # 写入配置信息
                  f.write(f"# 邮件服务器配置n")
                  f.write(f"MAIL_SERVER = '{self.server_var.get()}'n")
                  f.write(f"MAIL_PORT = {self.port_var.get()}n")
                  f.write(f"MAIL_USE_TLS = {self.use_tls_var.get()}n")
                  f.write(f"MAIL_USERNAME = '{self.username_var.get()}'n")
                  f.write(f"MAIL_PASSWORD = '{self.password_var.get()}'  # 注意:这是应用专用密码,不是登录密码n")
                  f.write(f"MAIL_DEFAULT_SENDER = '{self.sender_var.get()}'n")
      
              messagebox.showinfo("成功", "配置已保存")
              self.status_var.set("配置已保存")
          except Exception as e:
              messagebox.showerror("错误", f"保存配置失败: {str(e)}")
              self.status_var.set(f"保存配置失败: {str(e)}")
      
      

      3.测试SMTP连接

      def test_connection(self):
          """测试SMTP连接"""
          try:
              import smtplib
              import socket
      
              self.status_var.set("正在测试SMTP连接...")
              self.update_idletasks()
      
              server = self.server_var.get()
              port = self.port_var.get()
              use_tls = self.use_tls_var.get()
              username = self.username_var.get()
              password = self.password_var.get()
      
              # 设置超时时间
              socket.setdefaulttimeout(10)
      
              # 连接到SMTP服务器
              if port == 465:
                  # 使用SSL
                  smtp = smtplib.SMTP_SSL(server, port, timeout=10)
              else:
                  # 普通连接
                  smtp = smtplib.SMTP(server, port, timeout=10)
      
                  # 如果使用TLS
                  if use_tls:
                      smtp.starttls()
      
              # 登录
              smtp.login(username, password)
      
              # 关闭连接
              smtp.quit()
      
              messagebox.showinfo("成功", "SMTP连接测试成功!")
              self.status_var.set("SMTP连接测试成功")
          except socket.timeout:
              messagebox.showerror("错误", "连接超时,请检查服务器地址和端口")
              self.status_var.set("SMTP连接超时")
          except smtplib.SMTPAuthenticationError:
              messagebox.showerror("错误", "认证失败,请检查用户名和密码")
              self.status_var.set("SMTP认证失败")
          except Exception as e:
              messagebox.showerror("错误", f"连接测试失败: {str(e)}")
              self.status_var.set(f"连接测试失败: {str(e)}")
      

      知识点:

      邮件对象构建:使用MIMEMultipart创建包含多部分内容的邮件

      文件操作:读取附件文件并添加到邮件中

      异常处理:捕获并记录邮件发送过程中的错误

      上下文管理器:使用with语句自动关闭SMTP连接

      def create_simple_email_tab(self):
          """创建简单邮件标签页"""
          tab = ttk.Frame(self.notebook)
          self.notebook.add(tab, text="简单邮件")
      
          # 收件人,
          ttk.Label(tab, text="收件人:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
          self.simple_to_var = tk.StringVar()
          # 收件人输入框
          ttk.Entry(tab, textvariable=self.simple_to_var, width=50).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 主题
          ttk.Label(tab, text="主题:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
          self.simple_subject_var = tk.StringVar()
          ttk.Entry(tab, textvariable=self.simple_subject_var, width=50).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 正文
          ttk.Label(tab, text="正文:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
          self.simple_body_text = scrolledtext.ScrolledText(tab, width=50, height=15)
          self.simple_body_text.grid(row=2, column=1, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
      
          # 发送按钮
          ttk.Button(tab, text="发送", command=self.send_simple_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10)
      
          # 设置权重
          tab.columnconfigure(1, weight=1)
          tab.rowconfigure(2, weight=1)
      
      def create_html_email_tab(self):
          """创建HTML邮件标签页"""
          tab = ttk.Frame(self.notebook)
          self.notebook.add(tab, text="HTML邮件")
      
          # 收件人
          ttk.Label(tab, text="收件人:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
          self.html_to_var = tk.StringVar()
          ttk.Entry(tab, textvariable=self.html_to_var, width=50).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 主题
          ttk.Label(tab, text="主题:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
          self.html_subject_var = tk.StringVar()
          ttk.Entry(tab, textvariable=self.html_subject_var, width=50).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # HTML正文
          ttk.Label(tab, text="HTML正文:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
          self.html_body_text = scrolledtext.ScrolledText(tab, width=50, height=15)
          self.html_body_text.grid(row=2, column=1, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
      
          # 发送按钮
          ttk.Button(tab, text="发送", command=self.send_html_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10)
      
          # 设置权重
          tab.columnconfigure(1, weight=1)
          tab.rowconfigure(2, weight=1)
      
      def create_attachment_email_tab(self):
          """创建带附件邮件标签页"""
          tab = ttk.Frame(self.notebook)
          self.notebook.add(tab, text="带附件邮件")
      
          # 收件人
          ttk.Label(tab, text="收件人:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
          self.attach_to_var = tk.StringVar()
          ttk.Entry(tab, textvariable=self.attach_to_var, width=50).grid(row=0, column=1, columnspan=2, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 主题
          ttk.Label(tab, text="主题:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
          self.attach_subject_var = tk.StringVar()
          ttk.Entry(tab, textvariable=self.attach_subject_var, width=50).grid(row=1, column=1, columnspan=2, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 正文
          ttk.Label(tab, text="正文:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
          self.attach_body_text = scrolledtext.ScrolledText(tab, width=50, height=10)
          self.attach_body_text.grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
      
          # 附件列表
          ttk.Label(tab, text="附件:").grid(row=3, column=0, sticky=tk.NW, padx=5, pady=5)
          self.attach_files_listbox = tk.Listbox(tab, width=50, height=5)
          self.attach_files_listbox.grid(row=3, column=1, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
      
          # 附件按钮
          attach_buttons_frame = ttk.Frame(tab)
          attach_buttons_frame.grid(row=3, column=2, sticky=tk.N, padx=5, pady=5)
      
          ttk.Button(attach_buttons_frame, text="添加", command=self.add_attachment).pack(fill=tk.X, pady=2)
          ttk.Button(attach_buttons_frame, text="删除", command=self.remove_attachment).pack(fill=tk.X, pady=2)
      
          # 发送按钮
          ttk.Button(tab, text="发送", command=self.send_attachment_email).grid(row=4, column=2, sticky=tk.E, padx=5, pady=10)
      
          # 设置权重
          tab.columnconfigure(1, weight=1)
          tab.rowconfigure(2, weight=1)
          tab.rowconfigure(3, weight=1)
      
      def create_bulk_email_tab(self):
          """创建批量发送邮件标签页"""
          tab = ttk.Frame(self.notebook)
          self.notebook.add(tab, text="批量发送")
      
          # CSV文件
          ttk.Label(tab, text="CSV文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
          self.bulk_csv_var = tk.StringVar()
          ttk.Entry(tab, textvariable=self.bulk_csv_var, width=50).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
          ttk.Button(tab, text="浏览...", command=self.browse_csv).grid(row=0, column=2, padx=5, pady=5)
      
          # 主题模板
          ttk.Label(tab, text="主题模板:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
          self.bulk_subject_var = tk.StringVar()
          ttk.Entry(tab, textvariable=self.bulk_subject_var, width=50).grid(row=1, column=1, columnspan=2, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 正文模板
          ttk.Label(tab, text="正文模板:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
          self.bulk_body_text = scrolledtext.ScrolledText(tab, width=50, height=15)
          self.bulk_body_text.grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
      
          # HTML格式
          self.bulk_html_var = tk.BooleanVar()
          ttk.Checkbutton(tab, text="HTML格式", variable=self.bulk_html_var).grid(row=3, column=1, sticky=tk.W, padx=5, pady=5)
      
          # 发送按钮
          ttk.Button(tab, text="发送", command=self.send_bulk_emails).grid(row=3, column=2, sticky=tk.E, padx=5, pady=10)
      
          # 设置权重
          tab.columnconfigure(1, weight=1)
          tab.rowconfigure(2, weight=1)
      
      def create_config_tab(self):
          """创建配置标签页"""
          tab = ttk.Frame(self.notebook)
          self.notebook.add(tab, text="配置")
      
          # 服务器
          ttk.Label(tab, text="SMTP服务器:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
          self.server_var = tk.StringVar(value=self.mail_config['server'])
          ttk.Entry(tab, textvariable=self.server_var, width=30).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 端口
          ttk.Label(tab, text="端口:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
          self.port_var = tk.IntVar(value=self.mail_config['port'])
          ttk.Entry(tab, textvariable=self.port_var, width=10).grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
      
          # 使用TLS
          self.use_tls_var = tk.BooleanVar(value=self.mail_config['use_tls'])
          ttk.Checkbutton(tab, text="使用TLS", variable=self.use_tls_var).grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
      
          # 用户名
          ttk.Label(tab, text="用户名:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
          self.username_var = tk.StringVar(value=self.mail_config['username'])
          ttk.Entry(tab, textvariable=self.username_var, width=30).grid(row=3, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 密码
          ttk.Label(tab, text="授权密码:").grid(row=4, column=0, sticky=tk.W, padx=5, pady=5)
          self.password_var = tk.StringVar(value=self.mail_config['password'])
          ttk.Entry(tab, textvariable=self.password_var, width=30, show="*").grid(row=4, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 默认发件人
          ttk.Label(tab, text="默认发件人:").grid(row=5, column=0, sticky=tk.W, padx=5, pady=5)
          self.sender_var = tk.StringVar(value=self.mail_config['default_sender'])
          ttk.Entry(tab, textvariable=self.sender_var, width=30).grid(row=5, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
      
          # 按钮框架
          button_frame = ttk.Frame(tab)
          button_frame.grid(row=6, column=1, sticky=tk.E, padx=5, pady=10)
      
          # 测试连接按钮
          ttk.Button(button_frame, text="测试连接", command=self.test_connection).pack(side=tk.LEFT, padx=5)
      
          # 保存按钮
          ttk.Button(button_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT)
      
          # 设置权重
          tab.columnconfigure(1, weight=1)
      

      常用布局参数解释

      1.创建参数:

      • tab: 父容器(与标签相同)
      • textvariable=self.simple_to_var: 绑定到前面创建的字符串变量
      • width=50: 输入框宽度为50个字符单位

      2.布局参数 (.grid):

      • row=0: 放在网格的第0行
      • column=0: 放在网格的第0列
      • sticky=tk.W: 标签在网格单元格内靠左对齐(西侧)
      • padx=5: 水平方向(左右)各5像素的外边距
      • pady=5: 垂直方向(上下)各5像素的外边距

      #!/usr/bin/env python
      # -*- coding: utf-8 -*-
      
      """
      Python邮件自动发送工具 - 图形界面版
      功能:使用tkinter实现邮件发送图形界面,支持文本邮件、HTML邮件、带附件邮件和批量发送
      """
      # 导入tkinter模块,用于创建图形界面
      import tkinter as tk
      # 导入ttk模块,用于创建标签页控件
      from tkinter import ttk, filedialog, messagebox, scrolledtext
      # 导入os模块,用于文件操作
      import os
      # 导入csv模块,用于读取CSV文件
      import csv
      # 导入datetime模块,用于获取当前日期和时间
      from datetime import datetime
      # 导入logging模块,用于日志记录
      import logging
      # 导入sys模块,用于系统操作
      import sys
      
      class EmailSenderApp(tk.Tk):
          def __init__(self):
              super().__init__()
              # 设置窗口标题
              self.title("Python邮件自动发送工具")
              # 设置窗口大小
              self.geometry("800x600")
              # 设置窗口是否可调整大小
              self.resizable(True, True)
              # 加载配置
              self.mail_config = self.load_config()
              # 创建标签页控件
              self.notebook = ttk.Notebook(self)
              self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
              
              # 创建各个标签页
              self.create_simple_email_tab()
              self.create_html_email_tab()
              self.create_attachment_email_tab()
              self.create_bulk_email_tab()
              self.create_config_tab()
              
              # 状态栏
              self.status_var = tk.StringVar()
              # 设置状态栏初始状态
              self.status_var.set("就绪")
              # 创建状态栏
              self.status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
              # 将状态栏放置在底部
              self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
              
          def load_config(self):
              """加载配置"""
              try:
                  # 从config.py文件中导入配置
                  from config import (
                      MAIL_SERVER, MAIL_PORT, MAIL_USE_TLS,
                      MAIL_USERNAME, MAIL_PASSWORD, MAIL_DEFAULT_SENDER
                  )
                  # 将配置数据转换为字典
                  config_data = {
                      'server': MAIL_SERVER,
                      'port': MAIL_PORT,
                      'use_tls': MAIL_USE_TLS,
                      'username': MAIL_USERNAME,
                      'password': MAIL_PASSWORD,
                      'default_sender': MAIL_DEFAULT_SENDER
                  }
                  # 返回配置数据(字典格式)
                  return config_data
              except ImportError:
                  # 如果无法加载配置文件,则返回默认配置
                  messagebox.showerror("错误", "无法加载配置文件,请检查config.py是否存在")
                  return {
                      'server': 'smtp.qq.com',
                      'port': 465,
                      'use_tls': False,
                      'username': '2949666522@qq.com',
                      'password': 'ydevxpkfezjydddd',
                      'default_sender': '2949666522@qq.com'
                  }
          
          def save_config(self):
              """修改配置文件,保存配置到config.py文件"""
              try:
                  # 打开config.py文件,准备写入配置信息
                  with open('config.py', 'w', encoding='utf-8') as f:
                      # 写入配置信息
                      f.write(f"# 邮件服务器配置n")
                      f.write(f"MAIL_SERVER = '{self.server_var.get()}'n")
                      f.write(f"MAIL_PORT = {self.port_var.get()}n")
                      f.write(f"MAIL_USE_TLS = {self.use_tls_var.get()}n")
                      f.write(f"MAIL_USERNAME = '{self.username_var.get()}'n")
                      f.write(f"MAIL_PASSWORD = '{self.password_var.get()}'  # 注意:这是应用专用密码,不是登录密码n")
                      f.write(f"MAIL_DEFAULT_SENDER = '{self.sender_var.get()}'n")
                  
                  messagebox.showinfo("成功", "配置已保存")
                  self.status_var.set("配置已保存")
              except Exception as e:
                  messagebox.showerror("错误", f"保存配置失败: {str(e)}")
                  self.status_var.set(f"保存配置失败: {str(e)}")
          
          def create_simple_email_tab(self):
              """创建简单邮件标签页"""
              tab = ttk.Frame(self.notebook)
              self.notebook.add(tab, text="简单邮件")
              
              # 收件人,
              ttk.Label(tab, text="收件人:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
              self.simple_to_var = tk.StringVar()
              # 收件人输入框
              ttk.Entry(tab, textvariable=self.simple_to_var, width=50).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 主题
              ttk.Label(tab, text="主题:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
              self.simple_subject_var = tk.StringVar()
              ttk.Entry(tab, textvariable=self.simple_subject_var, width=50).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 正文
              ttk.Label(tab, text="正文:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
              self.simple_body_text = scrolledtext.ScrolledText(tab, width=50, height=15)
              self.simple_body_text.grid(row=2, column=1, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
              
              # 发送按钮
              ttk.Button(tab, text="发送", command=self.send_simple_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10)
              
              # 设置权重
              tab.columnconfigure(1, weight=1)
              tab.rowconfigure(2, weight=1)
          
          def create_html_email_tab(self):
              """创建HTML邮件标签页"""
              tab = ttk.Frame(self.notebook)
              self.notebook.add(tab, text="HTML邮件")
              
              # 收件人
              ttk.Label(tab, text="收件人:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
              self.html_to_var = tk.StringVar()
              ttk.Entry(tab, textvariable=self.html_to_var, width=50).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 主题
              ttk.Label(tab, text="主题:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
              self.html_subject_var = tk.StringVar()
              ttk.Entry(tab, textvariable=self.html_subject_var, width=50).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # HTML正文
              ttk.Label(tab, text="HTML正文:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
              self.html_body_text = scrolledtext.ScrolledText(tab, width=50, height=15)
              self.html_body_text.grid(row=2, column=1, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
              
              # 发送按钮
              ttk.Button(tab, text="发送", command=self.send_html_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10)
              
              # 设置权重
              tab.columnconfigure(1, weight=1)
              tab.rowconfigure(2, weight=1)
          
          def create_attachment_email_tab(self):
              """创建带附件邮件标签页"""
              tab = ttk.Frame(self.notebook)
              self.notebook.add(tab, text="带附件邮件")
              
              # 收件人
              ttk.Label(tab, text="收件人:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
              self.attach_to_var = tk.StringVar()
              ttk.Entry(tab, textvariable=self.attach_to_var, width=50).grid(row=0, column=1, columnspan=2, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 主题
              ttk.Label(tab, text="主题:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
              self.attach_subject_var = tk.StringVar()
              ttk.Entry(tab, textvariable=self.attach_subject_var, width=50).grid(row=1, column=1, columnspan=2, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 正文
              ttk.Label(tab, text="正文:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
              self.attach_body_text = scrolledtext.ScrolledText(tab, width=50, height=10)
              self.attach_body_text.grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
              
              # 附件列表
              ttk.Label(tab, text="附件:").grid(row=3, column=0, sticky=tk.NW, padx=5, pady=5)
              self.attach_files_listbox = tk.Listbox(tab, width=50, height=5)
              self.attach_files_listbox.grid(row=3, column=1, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
              
              # 附件按钮
              attach_buttons_frame = ttk.Frame(tab)
              attach_buttons_frame.grid(row=3, column=2, sticky=tk.N, padx=5, pady=5)
              
              ttk.Button(attach_buttons_frame, text="添加", command=self.add_attachment).pack(fill=tk.X, pady=2)
              ttk.Button(attach_buttons_frame, text="删除", command=self.remove_attachment).pack(fill=tk.X, pady=2)
              
              # 发送按钮
              ttk.Button(tab, text="发送", command=self.send_attachment_email).grid(row=4, column=2, sticky=tk.E, padx=5, pady=10)
              
              # 设置权重
              tab.columnconfigure(1, weight=1)
              tab.rowconfigure(2, weight=1)
              tab.rowconfigure(3, weight=1)
          
          def create_bulk_email_tab(self):
              """创建批量发送邮件标签页"""
              tab = ttk.Frame(self.notebook)
              self.notebook.add(tab, text="批量发送")
              
              # CSV文件
              ttk.Label(tab, text="CSV文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
              self.bulk_csv_var = tk.StringVar()
              ttk.Entry(tab, textvariable=self.bulk_csv_var, width=50).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              ttk.Button(tab, text="浏览...", command=self.browse_csv).grid(row=0, column=2, padx=5, pady=5)
              
              # 主题模板
              ttk.Label(tab, text="主题模板:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
              self.bulk_subject_var = tk.StringVar()
              ttk.Entry(tab, textvariable=self.bulk_subject_var, width=50).grid(row=1, column=1, columnspan=2, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 正文模板
              ttk.Label(tab, text="正文模板:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
              self.bulk_body_text = scrolledtext.ScrolledText(tab, width=50, height=15)
              self.bulk_body_text.grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E+tk.N+tk.S, padx=5, pady=5)
              
              # HTML格式
              self.bulk_html_var = tk.BooleanVar()
              ttk.Checkbutton(tab, text="HTML格式", variable=self.bulk_html_var).grid(row=3, column=1, sticky=tk.W, padx=5, pady=5)
              
              # 发送按钮
              ttk.Button(tab, text="发送", command=self.send_bulk_emails).grid(row=3, column=2, sticky=tk.E, padx=5, pady=10)
              
              # 设置权重
              tab.columnconfigure(1, weight=1)
              tab.rowconfigure(2, weight=1)
          
          def create_config_tab(self):
              """创建配置标签页"""
              tab = ttk.Frame(self.notebook)
              self.notebook.add(tab, text="配置")
              
              # 服务器
              ttk.Label(tab, text="SMTP服务器:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
              self.server_var = tk.StringVar(value=self.mail_config['server'])
              ttk.Entry(tab, textvariable=self.server_var, width=30).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 端口
              ttk.Label(tab, text="端口:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
              self.port_var = tk.IntVar(value=self.mail_config['port'])
              ttk.Entry(tab, textvariable=self.port_var, width=10).grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
              
              # 使用TLS
              self.use_tls_var = tk.BooleanVar(value=self.mail_config['use_tls'])
              ttk.Checkbutton(tab, text="使用TLS", variable=self.use_tls_var).grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
              
              # 用户名
              ttk.Label(tab, text="用户名:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
              self.username_var = tk.StringVar(value=self.mail_config['username'])
              ttk.Entry(tab, textvariable=self.username_var, width=30).grid(row=3, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 密码
              ttk.Label(tab, text="授权密码:").grid(row=4, column=0, sticky=tk.W, padx=5, pady=5)
              self.password_var = tk.StringVar(value=self.mail_config['password'])
              ttk.Entry(tab, textvariable=self.password_var, width=30, show="*").grid(row=4, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 默认发件人
              ttk.Label(tab, text="默认发件人:").grid(row=5, column=0, sticky=tk.W, padx=5, pady=5)
              self.sender_var = tk.StringVar(value=self.mail_config['default_sender'])
              ttk.Entry(tab, textvariable=self.sender_var, width=30).grid(row=5, column=1, sticky=tk.W+tk.E, padx=5, pady=5)
              
              # 按钮框架
              button_frame = ttk.Frame(tab)
              button_frame.grid(row=6, column=1, sticky=tk.E, padx=5, pady=10)
              
              # 测试连接按钮
              ttk.Button(button_frame, text="测试连接", command=self.test_connection).pack(side=tk.LEFT, padx=5)
              
              # 保存按钮
              ttk.Button(button_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT)
              
              # 设置权重
              tab.columnconfigure(1, weight=1)
          
          def add_attachment(self):
              """添加附件"""
              files = filedialog.askopenfilenames(title="选择附件")
              for file in files:
                  self.attach_files_listbox.insert(tk.END, file)
          
          def remove_attachment(self):
              """删除选中的附件"""
              selected = self.attach_files_listbox.curselection()
              if selected:
                  for index in reversed(selected):
                      self.attach_files_listbox.delete(index)
          
          def browse_csv(self):
              """浏览CSV文件"""
              file = filedialog.askopenfilename(title="选择CSV文件", filetypes=[("CSV文件", "*.csv")])
              if file:
                  self.bulk_csv_var.set(file)
          
          def test_connection(self):
              """测试SMTP连接"""
              try:
                  import smtplib
                  import socket
                  
                  self.status_var.set("正在测试SMTP连接...")
                  self.update_idletasks()
                  
                  server = self.server_var.get()
                  port = self.port_var.get()
                  use_tls = self.use_tls_var.get()
                  username = self.username_var.get()
                  password = self.password_var.get()
                  
                  # 设置超时时间
                  socket.setdefaulttimeout(10)
                  
                  # 连接到SMTP服务器
                  if port == 465:
                      # 使用SSL
                      smtp = smtplib.SMTP_SSL(server, port, timeout=10)
                  else:
                      # 普通连接
                      smtp = smtplib.SMTP(server, port, timeout=10)
                      
                      # 如果使用TLS
                      if use_tls:
                          smtp.starttls()
                  
                  # 登录
                  smtp.login(username, password)
                  
                  # 关闭连接
                  smtp.quit()
                  
                  messagebox.showinfo("成功", "SMTP连接测试成功!")
                  self.status_var.set("SMTP连接测试成功")
              except socket.timeout:
                  messagebox.showerror("错误", "连接超时,请检查服务器地址和端口")
                  self.status_var.set("SMTP连接超时")
              except smtplib.SMTPAuthenticationError:
                  messagebox.showerror("错误", "认证失败,请检查用户名和密码")
                  self.status_var.set("SMTP认证失败")
              except Exception as e:
                  messagebox.showerror("错误", f"连接测试失败: {str(e)}")
                  self.status_var.set(f"连接测试失败: {str(e)}")
          
          def send_simple_email(self):
              """发送简单邮件"""
              try:
                  # 使用当前配置创建EmailSender实例
                  sender = self.create_email_sender()
                  # 获取收件人
                  to_addr = self.simple_to_var.get()
                  # 获取主题
                  subject = self.simple_subject_var.get()
                  # 获取正文
                  body = self.simple_body_text.get(1.0, tk.END)
                  # 如果收件人、主题和正文为空,则提示错误
                  if not to_addr or not subject or not body.strip():
                      messagebox.showerror("错误", "收件人、主题和正文不能为空")
                      return
                  
                  self.status_var.set("正在发送邮件...")
                  self.update_idletasks()
                  
                  if sender.send_email(to_addr, subject, body):
                      messagebox.showinfo("成功", "邮件发送成功!")
                      self.status_var.set("邮件发送成功")
                  else:
                      messagebox.showerror("错误", "邮件发送失败,请查看日志获取详细信息。")
                      self.status_var.set("邮件发送失败")
              except Exception as e:
                  messagebox.showerror("错误", f"发送邮件时出错: {str(e)}")
                  self.status_var.set(f"发送邮件时出错: {str(e)}")
          
          def send_html_email(self):
              """发送HTML邮件"""
              try:
                  # 使用当前配置创建EmailSender实例
                  sender = self.create_email_sender()
                  
                  to_addr = self.html_to_var.get()
                  subject = self.html_subject_var.get()
                  body = self.html_body_text.get(1.0, tk.END)
                  
                  if not to_addr or not subject or not body.strip():
                      messagebox.showerror("错误", "收件人、主题和正文不能为空")
                      return
                  
                  self.status_var.set("正在发送HTML邮件...")
                  self.update_idletasks()
                  
                  if sender.send_email(to_addr, subject, body, html=True):
                      messagebox.showinfo("成功", "HTML邮件发送成功!")
                      self.status_var.set("HTML邮件发送成功")
                  else:
                      messagebox.showerror("错误", "邮件发送失败,请查看日志获取详细信息。")
                      self.status_var.set("邮件发送失败")
              except Exception as e:
                  messagebox.showerror("错误", f"发送邮件时出错: {str(e)}")
                  self.status_var.set(f"发送邮件时出错: {str(e)}")
          
          def send_attachment_email(self):
              """发送带附件的邮件"""
              try:
                  # 使用当前配置创建EmailSender实例
                  sender = self.create_email_sender()
                  
                  to_addr = self.attach_to_var.get()
                  subject = self.attach_subject_var.get()
                  body = self.attach_body_text.get(1.0, tk.END)
                  
                  if not to_addr or not subject or not body.strip():
                      messagebox.showerror("错误", "收件人、主题和正文不能为空")
                      return
                  
                  attachments = list(self.attach_files_listbox.get(0, tk.END))
                  
                  self.status_var.set("正在发送带附件的邮件...")
                  self.update_idletasks()
                  
                  if sender.send_email(to_addr, subject, body, attachments=attachments):
                      messagebox.showinfo("成功", "带附件的邮件发送成功!")
                      self.status_var.set("带附件的邮件发送成功")
                  else:
                      messagebox.showerror("错误", "邮件发送失败,请查看日志获取详细信息。")
                      self.status_var.set("邮件发送失败")
              except Exception as e:
                  messagebox.showerror("错误", f"发送邮件时出错: {str(e)}")
                  self.status_var.set(f"发送邮件时出错: {str(e)}")
          
          def send_bulk_emails(self):
              """批量发送邮件"""
              try:
                  # 使用当前配置创建EmailSender实例
                  sender = self.create_email_sender()
                  # 获取批量发送邮件的配置
                  csv_file = self.bulk_csv_var.get()
                  # 获取主题模板
                  subject_template = self.bulk_subject_var.get()
                  # 获取正文模板
                  body_template = self.bulk_body_text.get(1.0, tk.END)
                  # 获取是否为HTML格式
                  html = self.bulk_html_var.get()
                  # 如果CSV文件、主题模板和正文模板为空,则提示错误
                  if not csv_file or not subject_template or not body_template.strip():
                      messagebox.showerror("错误", "CSV文件、主题模板和正文模板不能为空")
                      return
                  # 如果CSV文件不存在,则提示错误
                  if not os.path.isfile(csv_file):
                      messagebox.showerror("错误", f"文件不存在: {csv_file}")
                      return
                  
                  # 读取CSV文件
                  recipients = []
                  with open(csv_file, 'r', encoding='utf-8') as f:
                      reader = csv.DictReader(f)
                      for row in reader:
                          recipients.append(row)
                  # 如果CSV文件为空或格式不正确,则提示错误
                  if not recipients:
                      messagebox.showerror("错误", "CSV文件为空或格式不正确。")
                      return
                  # 如果CSV文件为空或格式不正确,则提示错误
                  self.status_var.set("正在批量发送邮件...")
                  self.update_idletasks()
                  
                  # 批量发送邮件
                  success_count = sender.send_bulk_emails(recipients, subject_template, body_template, html=html)
                  
                  messagebox.showinfo("成功", f"批量发送完成,成功发送 {success_count}/{len(recipients)} 封邮件。")
                  self.status_var.set(f"批量发送完成,成功: {success_count}/{len(recipients)}")
                  
              except Exception as e:
                  messagebox.showerror("错误", f"批量发送邮件时出错: {str(e)}")
                  self.status_var.set(f"批量发送邮件时出错: {str(e)}")
          
          def create_email_sender(self):
              """创建临时配置对象"""
              # 创建一个临时的配置对象
              class TempConfig:
                  def __init__(self, app):
                      self.MAIL_SERVER = app.server_var.get()
                      self.MAIL_PORT = app.port_var.get()
                      self.MAIL_USE_TLS = app.use_tls_var.get()
                      self.MAIL_USERNAME = app.username_var.get()
                      self.MAIL_PASSWORD = app.password_var.get()
                      self.MAIL_DEFAULT_SENDER = app.sender_var.get()
              
      
              class CustomEmailSender():
                  def __init__(self, config):
                      self.server = config.MAIL_SERVER
                      self.port = config.MAIL_PORT
                      self.use_tls = config.MAIL_USE_TLS
                      self.username = config.MAIL_USERNAME
                      self.password = config.MAIL_PASSWORD
                      self.default_sender = config.MAIL_DEFAULT_SENDER
                      
                      # 设置日志
                      self._setup_logging()
                  def _setup_logging(self):
                      """设置日志记录"""
                      log_dir = "logs"
                      # 如果日志目录不存在,则创建日志目录
                      if not os.path.exists(log_dir):
                          os.makedirs(log_dir)
                      # 创建日志文件
                      log_file = os.path.join(log_dir, f"email_sender_{datetime.now().strftime('%Y%m%d')}.log")
                      # 配置日志记录
                      logging.basicConfig(
                          # 设置日志级别
                          level=logging.INFO,
                          # 设置日志格式
                          format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                          # 设置日志处理器
                          handlers=[
                              # 设置日志文件处理器
                              logging.FileHandler(log_file),
                              # 设置日志控制台处理器
                              logging.StreamHandler(sys.stdout)
                          ]
                      )
                      # 获取日志记录器
                      self.logger = logging.getLogger("EmailSender")  
                  def send_email(self, to_addrs, subject, body, attachments=None, cc=None, bcc=None, html=False):
                      """发送邮件(单个收件人)"""
                      import smtplib
                      from email.mime.text import MIMEText
                      from email.mime.multipart import MIMEMultipart
                      from email.mime.application import MIMEApplication
                      
                      # 转换地址格式
                      if isinstance(to_addrs, str):
                          to_addrs = [to_addrs]
                      # 如果抄送地址为字符串,则转换为列表
                      if isinstance(cc, str):
                          cc = [cc]
                      # 如果抄送地址为None,则设置为空列表
                      elif cc is None:
                          cc = []        
                      if isinstance(bcc, str):
                          bcc = [bcc]
                      elif bcc is None:
                          bcc = []
                      
                      # 创建邮件对象
                      msg = MIMEMultipart()
                      msg['From'] = self.default_sender
                      msg['To'] = ', '.join(to_addrs)
                      msg['Subject'] = subject
                      
                      if cc:
                          msg['Cc'] = ', '.join(cc)
                          
                      # 添加邮件正文
                      if html:
                          msg.attach(MIMEText(body, 'html', 'utf-8'))
                      else:
                          msg.attach(MIMEText(body, 'plain', 'utf-8'))
                      
                      # 添加附件
                      if attachments:
                          for file_path in attachments:
                              if os.path.isfile(file_path):
                                  with open(file_path, 'rb') as f:
                                      attachment = MIMEApplication(f.read())
                                      attachment.add_header(
                                          'Content-Disposition', 
                                          'attachment', 
                                          filename=os.path.basename(file_path)
                                      )
                                      msg.attach(attachment)
                              else:
                                  self.logger.warning(f"附件不存在: {file_path}")
                      
                      # 所有收件人列表(包括抄送和密送)
                      all_recipients = to_addrs + cc + bcc
                      
                      try:
                          # 连接到SMTP服务器 - 修复连接问题
                          if self.port == 465:
                              # 使用SSL
                              server = smtplib.SMTP_SSL(self.server, self.port, timeout=30)
                          else:
                              # 普通连接
                              server = smtplib.SMTP(self.server, self.port, timeout=30)
                              
                              # 如果使用TLS
                              if self.use_tls:
                                  server.starttls()
                          
                          # 登录
                          server.login(self.username, self.password)
                          
                          # 发送邮件
                          server.sendmail(self.default_sender, all_recipients, msg.as_string())
                          
                          # 关闭连接
                          server.quit()
                          
                          self.logger.info(f"邮件已成功发送给: {', '.join(to_addrs)}")
                          return True
                          
                      except Exception as e:
                          self.logger.error(f"发送邮件失败: {str(e)}")
                          return False
                  
                  def send_bulk_emails(self, recipients_data, subject_template, body_template, attachments=None, html=False):
                      """批量发送邮件"""
                      # 成功发送的邮件数量
                      success_count = 0
                      # 遍历收件人数据
                      for recipient_data in recipients_data:
                          # 获取收件人邮箱地址
                          to_addr = recipient_data.get('email')
                          # 如果收件人邮箱地址为空,则跳过此条记录
                          if not to_addr:
                              self.logger.warning("缺少收件人邮箱地址,跳过此条记录")
                              continue
                              
                          # 替换模板变量
                          subject = subject_template
                          body = body_template
                          # 遍历收件人数据
                          for key, value in recipient_data.items():
                              # 如果key不是邮箱地址,则替换模板变量
                              if key != 'email':
                                  placeholder = f"{{{key}}}"
                                  subject = subject.replace(placeholder, str(value))
                                  body = body.replace(placeholder, str(value))
      
                          # 发送邮件
                          if self.send_email(to_addr, subject, body, attachments=attachments, html=html):
                              success_count += 1
                              
                      # 返回成功发送的邮件数量
                      return success_count
              
              # 创建并返回自定义CustomEmailSender实例
              config = TempConfig(self)
              return CustomEmailSender(config)
      
      
      if __name__ == "__main__":
          # 创建EmailSenderApp实例
          app = EmailSenderApp()
          # 启动主循环
          app.mainloop() 
      

      通过本教程,我们学习了如何使用Python的smtplib库开发一个功能完整的邮件自动发送工具。主要涵盖了以下知识点:

      • SMTP协议基础知识
      • 使用smtplib连接邮件服务器
      • 创建和发送不同类型的邮件(文本、HTML、附件)
      • 批量发送个性化邮件
      • 异常处理和日志记录
      • 从配置文件加载设置

      这个邮件发送工具可以应用于多种场景,如系统通知、营销邮件、报表自动发送等。通过进一步扩展,还可以实现更复杂的功能,满足不同的业务需求。

      以上就是Python编写邮件自动发送工具的完整指南的详细内容,更多关于Python邮件自动发送的资料请关注风君子博客其它相关文章!

      您可能感兴趣的文章:

      • python使用QQ邮箱实现自动发送邮件
      • Python实现自动化邮件发送过程详解
      • Python实战之自动发送邮件的实现
      • Python自动化办公之定时发送邮件的实现
      • 懒人必备Python代码之自动发送邮件
      • Python利用脚本实现自动发送电子邮件

      站内搜索