代码实现与知识点解析
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邮箱作为服务端,其配置如下:
找到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邮箱)要求使用应用专用密码而非登录密码
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: # 使用当前配置创建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.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)
常用布局参数解释
创建参数:
tab
: 父容器(与标签相同)textvariable=self.simple_to_var
: 绑定到前面创建的字符串变量width=50
: 输入框宽度为50个字符单位
布局参数 (.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 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()
7.效果图
总结
通过本教程,我们学习了如何使用python的smtplib
库开发一个功能完整的邮件自动发送工具。主要涵盖了以下知识点:
- smtp协议基础知识
- 使用
smtplib
连接邮件服务器 - 创建和发送不同类型的邮件(文本、html、附件)
- 批量发送个性化邮件
- 异常处理和日志记录
- 从配置文件加载设置
这个邮件发送工具可以应用于多种场景,如系统通知、营销邮件、报表自动发送等。通过进一步扩展,还可以实现更复杂的功能,满足不同的业务需求。
以上就是python使用smtplib库开发一个邮件自动发送工具的详细内容,更多关于python smtplib邮件自动发送的资料请关注代码网其它相关文章!
发表评论