开发者

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

目录
  • 代码实现与知识点解析
    • 1. 导入必要的库
    • 2. 配置邮件服务器参数
    • 3. 创建邮件发送类
    • 4. 实现邮件发送功能
    • 5. 实现视图
    • 6.总代码
    • 7.效果图
  • 总结

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

    代码实现与知识点解析

    1. 导入必要的库

    # 导入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
    

    2. 配置邮件服务器参数

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

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

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

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

    将配置内容写在一个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邮箱)要求使用应用专用密码而非登录密码

    3. 创建邮件发送类

    • 日志
    • 发送邮件(单个收件人)
    • 发送邮件(批量)
    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)

    4. 实现邮件发送功能

    简单邮件发送

    def send_simple_email(self):
        """发送简单邮件"""
        try:
           javascript # 使用当前配置创建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连接

    5. 实现视图

    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.Laandroidbel(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.bulkandroid_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=tkjs.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像素的外边距

    6.总代码

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    """
    Python邮件自动发送工具 - 图形界面版
    功能:使用tkinter实现邮件发送图形界面,支持文本邮件、HTML邮件、带附件邮件和批量发送
    """
    # 导入tkinter模块,用于创建图形界面
    import tkinter as tk
    # 导入ttk模块,用于创建标签页控件
    from tkinter import phpttk, 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() 
    

    7.效果图

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

    总结

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

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

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

    以上就是Python编写邮件自动发送工具的完整指南的详细内容,更多关于Python邮件自动发送的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜