Python編寫(xiě)郵件自動(dòng)發(fā)送工具的完整指南
自動(dòng)化郵件發(fā)送是一個(gè)非常實(shí)用的功能。無(wú)論是系統(tǒng)通知、營(yíng)銷郵件、還是日常工作報(bào)告,Python的smtplib
庫(kù)都能幫助我們輕松實(shí)現(xiàn)這些功能。本教程將詳細(xì)介紹如何使用Python的smtplib
庫(kù)開(kāi)發(fā)一個(gè)功能完整的郵件自動(dòng)發(fā)送工具。
代碼實(shí)現(xiàn)與知識(shí)點(diǎn)解析
1. 導(dǎo)入必要的庫(kù)
# 導(dǎo)入tkinter模塊,用于創(chuàng)建圖形界面 import tkinter as tk # 導(dǎo)入ttk模塊,用于創(chuàng)建標(biāo)簽頁(yè)控件 from tkinter import ttk, filedialog, messagebox, scrolledtext # 導(dǎo)入os模塊,用于文件操作 import os # 導(dǎo)入csv模塊,用于讀取CSV文件 import csv # 導(dǎo)入datetime模塊,用于獲取當(dāng)前日期和時(shí)間 from datetime import datetime # 導(dǎo)入logging模塊,用于日志記錄 import logging # 導(dǎo)入sys模塊,用于系統(tǒng)操作 import sys
2. 配置郵件服務(wù)器參數(shù)
SMTP(Simple Mail Transfer Protocol)是一種簡(jiǎn)單的郵件傳輸協(xié)議,用于在服務(wù)器之間發(fā)送電子郵件。在Python中,我們可以使用smtplib庫(kù)來(lái)實(shí)現(xiàn)SMTP客戶端功能,與郵件服務(wù)器進(jìn)行通信。這里我選的是QQ郵箱作為服務(wù)端,其配置如下:
找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務(wù)
,開(kāi)啟服務(wù)并獲取授權(quán)碼
將配置內(nèi)容寫(xiě)在一個(gè)config.py文件中
MAIL_SERVER = 'smtp.qq.com' # 郵件服務(wù)器地址 MAIL_PORT = 465 # 郵件服務(wù)器端口 MAIL_USE_TLS = False # 是否使用TLS MAIL_USERNAME = '******@qq.com' # 郵件服務(wù)器用戶名 MAIL_PASSWORD = 'ydevxpkfezjyaddd' # 郵件服務(wù)器密碼 MAIL_DEFAULT_SENDER = '*******@qq.com' # 郵件服務(wù)器默認(rèn)發(fā)件人
注意:授權(quán)碼不是密碼
知識(shí)點(diǎn):
- SMTP服務(wù)器地址和端口:不同郵箱服務(wù)商有不同的服務(wù)器地址和端口
- TLS加密:保護(hù)郵件傳輸安全
- 應(yīng)用專用密碼:許多郵箱服務(wù)商(如QQ郵箱)要求使用應(yīng)用專用密碼而非登錄密碼
3. 創(chuàng)建郵件發(fā)送類
- 日志
- 發(fā)送郵件(單個(gè)收件人)
- 發(fā)送郵件(批量)
def create_email_sender(self): """創(chuàng)建臨時(shí)配置對(duì)象""" # 創(chuàng)建一個(gè)臨時(shí)的配置對(duì)象 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 # 設(shè)置日志 self._setup_logging() def _setup_logging(self): """設(shè)置日志記錄""" log_dir = "logs" # 如果日志目錄不存在,則創(chuàng)建日志目錄 if not os.path.exists(log_dir): os.makedirs(log_dir) # 創(chuàng)建日志文件 log_file = os.path.join(log_dir, f"email_sender_{datetime.now().strftime('%Y%m%d')}.log") # 配置日志記錄 logging.basicConfig( # 設(shè)置日志級(jí)別 level=logging.INFO, # 設(shè)置日志格式 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # 設(shè)置日志處理器 handlers=[ # 設(shè)置日志文件處理器 logging.FileHandler(log_file), # 設(shè)置日志控制臺(tái)處理器 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): """發(fā)送郵件(單個(gè)收件人)""" import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication # 轉(zhuǎn)換地址格式 if isinstance(to_addrs, str): to_addrs = [to_addrs] # 如果抄送地址為字符串,則轉(zhuǎn)換為列表 if isinstance(cc, str): cc = [cc] # 如果抄送地址為None,則設(shè)置為空列表 elif cc is None: cc = [] if isinstance(bcc, str): bcc = [bcc] elif bcc is None: bcc = [] # 創(chuàng)建郵件對(duì)象 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服務(wù)器 - 修復(fù)連接問(wèn)題 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) # 發(fā)送郵件 server.sendmail(self.default_sender, all_recipients, msg.as_string()) # 關(guān)閉連接 server.quit() self.logger.info(f"郵件已成功發(fā)送給: {', '.join(to_addrs)}") return True except Exception as e: self.logger.error(f"發(fā)送郵件失敗: {str(e)}") return False def send_bulk_emails(self, recipients_data, subject_template, body_template, attachments=None, html=False): """批量發(fā)送郵件""" # 成功發(fā)送的郵件數(shù)量 success_count = 0 # 遍歷收件人數(shù)據(jù) for recipient_data in recipients_data: # 獲取收件人郵箱地址 to_addr = recipient_data.get('email') # 如果收件人郵箱地址為空,則跳過(guò)此條記錄 if not to_addr: self.logger.warning("缺少收件人郵箱地址,跳過(guò)此條記錄") continue # 替換模板變量 subject = subject_template body = body_template # 遍歷收件人數(shù)據(jù) 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)) # 發(fā)送郵件 if self.send_email(to_addr, subject, body, attachments=attachments, html=html): success_count += 1 # 返回成功發(fā)送的郵件數(shù)量 return success_count # 創(chuàng)建并返回自定義CustomEmailSender實(shí)例 config = TempConfig(self) return CustomEmailSender(config)
4. 實(shí)現(xiàn)郵件發(fā)送功能
簡(jiǎn)單郵件發(fā)送
def send_simple_email(self): """發(fā)送簡(jiǎn)單郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 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) # 如果收件人、主題和正文為空,則提示錯(cuò)誤 if not to_addr or not subject or not body.strip(): messagebox.showerror("錯(cuò)誤", "收件人、主題和正文不能為空") return self.status_var.set("正在發(fā)送郵件...") self.update_idletasks() if sender.send_email(to_addr, subject, body): messagebox.showinfo("成功", "郵件發(fā)送成功!") self.status_var.set("郵件發(fā)送成功") else: messagebox.showerror("錯(cuò)誤", "郵件發(fā)送失敗,請(qǐng)查看日志獲取詳細(xì)信息。") self.status_var.set("郵件發(fā)送失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}")
HTML郵件
def send_html_email(self): """發(fā)送HTML郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 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("錯(cuò)誤", "收件人、主題和正文不能為空") return self.status_var.set("正在發(fā)送HTML郵件...") self.update_idletasks() if sender.send_email(to_addr, subject, body, html=True): messagebox.showinfo("成功", "HTML郵件發(fā)送成功!") self.status_var.set("HTML郵件發(fā)送成功") else: messagebox.showerror("錯(cuò)誤", "郵件發(fā)送失敗,請(qǐng)查看日志獲取詳細(xì)信息。") self.status_var.set("郵件發(fā)送失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}")
帶附件的郵件
1.刪除附件和發(fā)送附件
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.發(fā)送
def send_attachment_email(self): """發(fā)送帶附件的郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 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("錯(cuò)誤", "收件人、主題和正文不能為空") return attachments = list(self.attach_files_listbox.get(0, tk.END)) self.status_var.set("正在發(fā)送帶附件的郵件...") self.update_idletasks() if sender.send_email(to_addr, subject, body, attachments=attachments): messagebox.showinfo("成功", "帶附件的郵件發(fā)送成功!") self.status_var.set("帶附件的郵件發(fā)送成功") else: messagebox.showerror("錯(cuò)誤", "郵件發(fā)送失敗,請(qǐng)查看日志獲取詳細(xì)信息。") self.status_var.set("郵件發(fā)送失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}")
批量發(fā)送
1.添加CSV文件
def browse_csv(self): """瀏覽CSV文件""" file = filedialog.askopenfilename(title="選擇CSV文件", filetypes=[("CSV文件", "*.csv")]) if file: self.bulk_csv_var.set(file)
2.發(fā)送
def send_bulk_emails(self): """批量發(fā)送郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 sender = self.create_email_sender() # 獲取批量發(fā)送郵件的配置 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文件、主題模板和正文模板為空,則提示錯(cuò)誤 if not csv_file or not subject_template or not body_template.strip(): messagebox.showerror("錯(cuò)誤", "CSV文件、主題模板和正文模板不能為空") return # 如果CSV文件不存在,則提示錯(cuò)誤 if not os.path.isfile(csv_file): messagebox.showerror("錯(cuò)誤", 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文件為空或格式不正確,則提示錯(cuò)誤 if not recipients: messagebox.showerror("錯(cuò)誤", "CSV文件為空或格式不正確。") return # 如果CSV文件為空或格式不正確,則提示錯(cuò)誤 self.status_var.set("正在批量發(fā)送郵件...") self.update_idletasks() # 批量發(fā)送郵件 success_count = sender.send_bulk_emails(recipients, subject_template, body_template, html=html) messagebox.showinfo("成功", f"批量發(fā)送完成,成功發(fā)送 {success_count}/{len(recipients)} 封郵件。") self.status_var.set(f"批量發(fā)送完成,成功: {success_count}/{len(recipients)}") ??????? except Exception as e: messagebox.showerror("錯(cuò)誤", f"批量發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"批量發(fā)送郵件時(shí)出錯(cuò): {str(e)}")
配置
1.加載配置
def load_config(self): """加載配置""" try: # 從config.py文件中導(dǎo)入配置 from config import ( MAIL_SERVER, MAIL_PORT, MAIL_USE_TLS, MAIL_USERNAME, MAIL_PASSWORD, MAIL_DEFAULT_SENDER ) # 將配置數(shù)據(jù)轉(zhuǎn)換為字典 config_data = { 'server': MAIL_SERVER, 'port': MAIL_PORT, 'use_tls': MAIL_USE_TLS, 'username': MAIL_USERNAME, 'password': MAIL_PASSWORD, 'default_sender': MAIL_DEFAULT_SENDER } # 返回配置數(shù)據(jù)(字典格式) return config_data except ImportError: # 如果無(wú)法加載配置文件,則返回默認(rèn)配置 messagebox.showerror("錯(cuò)誤", "無(wú)法加載配置文件,請(qǐng)檢查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: # 打開(kāi)config.py文件,準(zhǔn)備寫(xiě)入配置信息 with open('config.py', 'w', encoding='utf-8') as f: # 寫(xiě)入配置信息 f.write(f"# 郵件服務(wù)器配置\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()}' # 注意:這是應(yīng)用專用密碼,不是登錄密碼\n") f.write(f"MAIL_DEFAULT_SENDER = '{self.sender_var.get()}'\n") messagebox.showinfo("成功", "配置已保存") self.status_var.set("配置已保存") except Exception as e: messagebox.showerror("錯(cuò)誤", f"保存配置失敗: {str(e)}") self.status_var.set(f"保存配置失敗: {str(e)}")
3.測(cè)試SMTP連接
def test_connection(self): """測(cè)試SMTP連接""" try: import smtplib import socket self.status_var.set("正在測(cè)試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() # 設(shè)置超時(shí)時(shí)間 socket.setdefaulttimeout(10) # 連接到SMTP服務(wù)器 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) # 關(guān)閉連接 smtp.quit() messagebox.showinfo("成功", "SMTP連接測(cè)試成功!") self.status_var.set("SMTP連接測(cè)試成功") except socket.timeout: messagebox.showerror("錯(cuò)誤", "連接超時(shí),請(qǐng)檢查服務(wù)器地址和端口") self.status_var.set("SMTP連接超時(shí)") except smtplib.SMTPAuthenticationError: messagebox.showerror("錯(cuò)誤", "認(rèn)證失敗,請(qǐng)檢查用戶名和密碼") self.status_var.set("SMTP認(rèn)證失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"連接測(cè)試失敗: {str(e)}") self.status_var.set(f"連接測(cè)試失敗: {str(e)}")
知識(shí)點(diǎn):
郵件對(duì)象構(gòu)建:使用MIMEMultipart創(chuàng)建包含多部分內(nèi)容的郵件
文件操作:讀取附件文件并添加到郵件中
異常處理:捕獲并記錄郵件發(fā)送過(guò)程中的錯(cuò)誤
上下文管理器:使用with語(yǔ)句自動(dòng)關(guān)閉SMTP連接
5. 實(shí)現(xiàn)視圖
def create_simple_email_tab(self): """創(chuàng)建簡(jiǎn)單郵件標(biāo)簽頁(yè)""" tab = ttk.Frame(self.notebook) self.notebook.add(tab, text="簡(jiǎn)單郵件") # 收件人, 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_simple_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) def create_html_email_tab(self): """創(chuàng)建HTML郵件標(biāo)簽頁(yè)""" 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_html_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) def create_attachment_email_tab(self): """創(chuàng)建帶附件郵件標(biāo)簽頁(yè)""" 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_attachment_email).grid(row=4, column=2, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) tab.rowconfigure(3, weight=1) def create_bulk_email_tab(self): """創(chuàng)建批量發(fā)送郵件標(biāo)簽頁(yè)""" tab = ttk.Frame(self.notebook) self.notebook.add(tab, text="批量發(fā)送") # 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_bulk_emails).grid(row=3, column=2, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) def create_config_tab(self): """創(chuàng)建配置標(biāo)簽頁(yè)""" tab = ttk.Frame(self.notebook) self.notebook.add(tab, text="配置") # 服務(wù)器 ttk.Label(tab, text="SMTP服務(wù)器:").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="授權(quán)密碼:").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) # 默認(rèn)發(fā)件人 ttk.Label(tab, text="默認(rèn)發(fā)件人:").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) # 測(cè)試連接按鈕 ttk.Button(button_frame, text="測(cè)試連接", command=self.test_connection).pack(side=tk.LEFT, padx=5) # 保存按鈕 ttk.Button(button_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1)
常用布局參數(shù)解釋
1.創(chuàng)建參數(shù):
- tab: 父容器(與標(biāo)簽相同)
- textvariable=self.simple_to_var: 綁定到前面創(chuàng)建的字符串變量
- width=50: 輸入框?qū)挾葹?0個(gè)字符單位
2.布局參數(shù) (.grid):
- row=0: 放在網(wǎng)格的第0行
- column=0: 放在網(wǎng)格的第0列
- sticky=tk.W: 標(biāo)簽在網(wǎng)格單元格內(nèi)靠左對(duì)齊(西側(cè))
- padx=5: 水平方向(左右)各5像素的外邊距
- pady=5: 垂直方向(上下)各5像素的外邊距
6.總代碼
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Python郵件自動(dòng)發(fā)送工具 - 圖形界面版 功能:使用tkinter實(shí)現(xiàn)郵件發(fā)送圖形界面,支持文本郵件、HTML郵件、帶附件郵件和批量發(fā)送 """ # 導(dǎo)入tkinter模塊,用于創(chuàng)建圖形界面 import tkinter as tk # 導(dǎo)入ttk模塊,用于創(chuàng)建標(biāo)簽頁(yè)控件 from tkinter import ttk, filedialog, messagebox, scrolledtext # 導(dǎo)入os模塊,用于文件操作 import os # 導(dǎo)入csv模塊,用于讀取CSV文件 import csv # 導(dǎo)入datetime模塊,用于獲取當(dāng)前日期和時(shí)間 from datetime import datetime # 導(dǎo)入logging模塊,用于日志記錄 import logging # 導(dǎo)入sys模塊,用于系統(tǒng)操作 import sys class EmailSenderApp(tk.Tk): def __init__(self): super().__init__() # 設(shè)置窗口標(biāo)題 self.title("Python郵件自動(dòng)發(fā)送工具") # 設(shè)置窗口大小 self.geometry("800x600") # 設(shè)置窗口是否可調(diào)整大小 self.resizable(True, True) # 加載配置 self.mail_config = self.load_config() # 創(chuàng)建標(biāo)簽頁(yè)控件 self.notebook = ttk.Notebook(self) self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 創(chuàng)建各個(gè)標(biāo)簽頁(yè) self.create_simple_email_tab() self.create_html_email_tab() self.create_attachment_email_tab() self.create_bulk_email_tab() self.create_config_tab() # 狀態(tài)欄 self.status_var = tk.StringVar() # 設(shè)置狀態(tài)欄初始狀態(tài) self.status_var.set("就緒") # 創(chuàng)建狀態(tài)欄 self.status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) # 將狀態(tài)欄放置在底部 self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) def load_config(self): """加載配置""" try: # 從config.py文件中導(dǎo)入配置 from config import ( MAIL_SERVER, MAIL_PORT, MAIL_USE_TLS, MAIL_USERNAME, MAIL_PASSWORD, MAIL_DEFAULT_SENDER ) # 將配置數(shù)據(jù)轉(zhuǎn)換為字典 config_data = { 'server': MAIL_SERVER, 'port': MAIL_PORT, 'use_tls': MAIL_USE_TLS, 'username': MAIL_USERNAME, 'password': MAIL_PASSWORD, 'default_sender': MAIL_DEFAULT_SENDER } # 返回配置數(shù)據(jù)(字典格式) return config_data except ImportError: # 如果無(wú)法加載配置文件,則返回默認(rèn)配置 messagebox.showerror("錯(cuò)誤", "無(wú)法加載配置文件,請(qǐng)檢查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: # 打開(kāi)config.py文件,準(zhǔn)備寫(xiě)入配置信息 with open('config.py', 'w', encoding='utf-8') as f: # 寫(xiě)入配置信息 f.write(f"# 郵件服務(wù)器配置\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()}' # 注意:這是應(yīng)用專用密碼,不是登錄密碼\n") f.write(f"MAIL_DEFAULT_SENDER = '{self.sender_var.get()}'\n") messagebox.showinfo("成功", "配置已保存") self.status_var.set("配置已保存") except Exception as e: messagebox.showerror("錯(cuò)誤", f"保存配置失敗: {str(e)}") self.status_var.set(f"保存配置失敗: {str(e)}") def create_simple_email_tab(self): """創(chuàng)建簡(jiǎn)單郵件標(biāo)簽頁(yè)""" tab = ttk.Frame(self.notebook) self.notebook.add(tab, text="簡(jiǎn)單郵件") # 收件人, 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_simple_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) def create_html_email_tab(self): """創(chuàng)建HTML郵件標(biāo)簽頁(yè)""" 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_html_email).grid(row=3, column=1, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) def create_attachment_email_tab(self): """創(chuàng)建帶附件郵件標(biāo)簽頁(yè)""" 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_attachment_email).grid(row=4, column=2, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) tab.rowconfigure(3, weight=1) def create_bulk_email_tab(self): """創(chuàng)建批量發(fā)送郵件標(biāo)簽頁(yè)""" tab = ttk.Frame(self.notebook) self.notebook.add(tab, text="批量發(fā)送") # 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) # 發(fā)送按鈕 ttk.Button(tab, text="發(fā)送", command=self.send_bulk_emails).grid(row=3, column=2, sticky=tk.E, padx=5, pady=10) # 設(shè)置權(quán)重 tab.columnconfigure(1, weight=1) tab.rowconfigure(2, weight=1) def create_config_tab(self): """創(chuàng)建配置標(biāo)簽頁(yè)""" tab = ttk.Frame(self.notebook) self.notebook.add(tab, text="配置") # 服務(wù)器 ttk.Label(tab, text="SMTP服務(wù)器:").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="授權(quán)密碼:").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) # 默認(rèn)發(fā)件人 ttk.Label(tab, text="默認(rèn)發(fā)件人:").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) # 測(cè)試連接按鈕 ttk.Button(button_frame, text="測(cè)試連接", command=self.test_connection).pack(side=tk.LEFT, padx=5) # 保存按鈕 ttk.Button(button_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT) # 設(shè)置權(quán)重 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): """測(cè)試SMTP連接""" try: import smtplib import socket self.status_var.set("正在測(cè)試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() # 設(shè)置超時(shí)時(shí)間 socket.setdefaulttimeout(10) # 連接到SMTP服務(wù)器 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) # 關(guān)閉連接 smtp.quit() messagebox.showinfo("成功", "SMTP連接測(cè)試成功!") self.status_var.set("SMTP連接測(cè)試成功") except socket.timeout: messagebox.showerror("錯(cuò)誤", "連接超時(shí),請(qǐng)檢查服務(wù)器地址和端口") self.status_var.set("SMTP連接超時(shí)") except smtplib.SMTPAuthenticationError: messagebox.showerror("錯(cuò)誤", "認(rèn)證失敗,請(qǐng)檢查用戶名和密碼") self.status_var.set("SMTP認(rèn)證失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"連接測(cè)試失敗: {str(e)}") self.status_var.set(f"連接測(cè)試失敗: {str(e)}") def send_simple_email(self): """發(fā)送簡(jiǎn)單郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 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) # 如果收件人、主題和正文為空,則提示錯(cuò)誤 if not to_addr or not subject or not body.strip(): messagebox.showerror("錯(cuò)誤", "收件人、主題和正文不能為空") return self.status_var.set("正在發(fā)送郵件...") self.update_idletasks() if sender.send_email(to_addr, subject, body): messagebox.showinfo("成功", "郵件發(fā)送成功!") self.status_var.set("郵件發(fā)送成功") else: messagebox.showerror("錯(cuò)誤", "郵件發(fā)送失敗,請(qǐng)查看日志獲取詳細(xì)信息。") self.status_var.set("郵件發(fā)送失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") def send_html_email(self): """發(fā)送HTML郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 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("錯(cuò)誤", "收件人、主題和正文不能為空") return self.status_var.set("正在發(fā)送HTML郵件...") self.update_idletasks() if sender.send_email(to_addr, subject, body, html=True): messagebox.showinfo("成功", "HTML郵件發(fā)送成功!") self.status_var.set("HTML郵件發(fā)送成功") else: messagebox.showerror("錯(cuò)誤", "郵件發(fā)送失敗,請(qǐng)查看日志獲取詳細(xì)信息。") self.status_var.set("郵件發(fā)送失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") def send_attachment_email(self): """發(fā)送帶附件的郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 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("錯(cuò)誤", "收件人、主題和正文不能為空") return attachments = list(self.attach_files_listbox.get(0, tk.END)) self.status_var.set("正在發(fā)送帶附件的郵件...") self.update_idletasks() if sender.send_email(to_addr, subject, body, attachments=attachments): messagebox.showinfo("成功", "帶附件的郵件發(fā)送成功!") self.status_var.set("帶附件的郵件發(fā)送成功") else: messagebox.showerror("錯(cuò)誤", "郵件發(fā)送失敗,請(qǐng)查看日志獲取詳細(xì)信息。") self.status_var.set("郵件發(fā)送失敗") except Exception as e: messagebox.showerror("錯(cuò)誤", f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"發(fā)送郵件時(shí)出錯(cuò): {str(e)}") def send_bulk_emails(self): """批量發(fā)送郵件""" try: # 使用當(dāng)前配置創(chuàng)建EmailSender實(shí)例 sender = self.create_email_sender() # 獲取批量發(fā)送郵件的配置 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文件、主題模板和正文模板為空,則提示錯(cuò)誤 if not csv_file or not subject_template or not body_template.strip(): messagebox.showerror("錯(cuò)誤", "CSV文件、主題模板和正文模板不能為空") return # 如果CSV文件不存在,則提示錯(cuò)誤 if not os.path.isfile(csv_file): messagebox.showerror("錯(cuò)誤", 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文件為空或格式不正確,則提示錯(cuò)誤 if not recipients: messagebox.showerror("錯(cuò)誤", "CSV文件為空或格式不正確。") return # 如果CSV文件為空或格式不正確,則提示錯(cuò)誤 self.status_var.set("正在批量發(fā)送郵件...") self.update_idletasks() # 批量發(fā)送郵件 success_count = sender.send_bulk_emails(recipients, subject_template, body_template, html=html) messagebox.showinfo("成功", f"批量發(fā)送完成,成功發(fā)送 {success_count}/{len(recipients)} 封郵件。") self.status_var.set(f"批量發(fā)送完成,成功: {success_count}/{len(recipients)}") except Exception as e: messagebox.showerror("錯(cuò)誤", f"批量發(fā)送郵件時(shí)出錯(cuò): {str(e)}") self.status_var.set(f"批量發(fā)送郵件時(shí)出錯(cuò): {str(e)}") def create_email_sender(self): """創(chuàng)建臨時(shí)配置對(duì)象""" # 創(chuàng)建一個(gè)臨時(shí)的配置對(duì)象 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 # 設(shè)置日志 self._setup_logging() def _setup_logging(self): """設(shè)置日志記錄""" log_dir = "logs" # 如果日志目錄不存在,則創(chuàng)建日志目錄 if not os.path.exists(log_dir): os.makedirs(log_dir) # 創(chuàng)建日志文件 log_file = os.path.join(log_dir, f"email_sender_{datetime.now().strftime('%Y%m%d')}.log") # 配置日志記錄 logging.basicConfig( # 設(shè)置日志級(jí)別 level=logging.INFO, # 設(shè)置日志格式 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # 設(shè)置日志處理器 handlers=[ # 設(shè)置日志文件處理器 logging.FileHandler(log_file), # 設(shè)置日志控制臺(tái)處理器 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): """發(fā)送郵件(單個(gè)收件人)""" import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication # 轉(zhuǎn)換地址格式 if isinstance(to_addrs, str): to_addrs = [to_addrs] # 如果抄送地址為字符串,則轉(zhuǎn)換為列表 if isinstance(cc, str): cc = [cc] # 如果抄送地址為None,則設(shè)置為空列表 elif cc is None: cc = [] if isinstance(bcc, str): bcc = [bcc] elif bcc is None: bcc = [] # 創(chuàng)建郵件對(duì)象 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服務(wù)器 - 修復(fù)連接問(wèn)題 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) # 發(fā)送郵件 server.sendmail(self.default_sender, all_recipients, msg.as_string()) # 關(guān)閉連接 server.quit() self.logger.info(f"郵件已成功發(fā)送給: {', '.join(to_addrs)}") return True except Exception as e: self.logger.error(f"發(fā)送郵件失敗: {str(e)}") return False def send_bulk_emails(self, recipients_data, subject_template, body_template, attachments=None, html=False): """批量發(fā)送郵件""" # 成功發(fā)送的郵件數(shù)量 success_count = 0 # 遍歷收件人數(shù)據(jù) for recipient_data in recipients_data: # 獲取收件人郵箱地址 to_addr = recipient_data.get('email') # 如果收件人郵箱地址為空,則跳過(guò)此條記錄 if not to_addr: self.logger.warning("缺少收件人郵箱地址,跳過(guò)此條記錄") continue # 替換模板變量 subject = subject_template body = body_template # 遍歷收件人數(shù)據(jù) 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)) # 發(fā)送郵件 if self.send_email(to_addr, subject, body, attachments=attachments, html=html): success_count += 1 # 返回成功發(fā)送的郵件數(shù)量 return success_count # 創(chuàng)建并返回自定義CustomEmailSender實(shí)例 config = TempConfig(self) return CustomEmailSender(config) if __name__ == "__main__": # 創(chuàng)建EmailSenderApp實(shí)例 app = EmailSenderApp() # 啟動(dòng)主循環(huán) app.mainloop()
7.效果圖
總結(jié)
通過(guò)本教程,我們學(xué)習(xí)了如何使用Python的smtplib庫(kù)開(kāi)發(fā)一個(gè)功能完整的郵件自動(dòng)發(fā)送工具。主要涵蓋了以下知識(shí)點(diǎn):
- SMTP協(xié)議基礎(chǔ)知識(shí)
- 使用smtplib連接郵件服務(wù)器
- 創(chuàng)建和發(fā)送不同類型的郵件(文本、HTML、附件)
- 批量發(fā)送個(gè)性化郵件
- 異常處理和日志記錄
- 從配置文件加載設(shè)置
這個(gè)郵件發(fā)送工具可以應(yīng)用于多種場(chǎng)景,如系統(tǒng)通知、營(yíng)銷郵件、報(bào)表自動(dòng)發(fā)送等。通過(guò)進(jìn)一步擴(kuò)展,還可以實(shí)現(xiàn)更復(fù)雜的功能,滿足不同的業(yè)務(wù)需求。
以上就是Python編寫(xiě)郵件自動(dòng)發(fā)送工具的完整指南的詳細(xì)內(nèi)容,更多關(guān)于Python郵件自動(dòng)發(fā)送的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用keras加載訓(xùn)練好的.H5文件,并實(shí)現(xiàn)預(yù)測(cè)圖片
今天小編就為大家分享一篇利用keras加載訓(xùn)練好的.H5文件,并實(shí)現(xiàn)預(yù)測(cè)圖片,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-01-01python星號(hào)(*)和雙星號(hào)(**)?函數(shù)動(dòng)態(tài)參數(shù)匹配及解包操作方法
這篇文章主要介紹了python星號(hào)(*)和雙星號(hào)(**)?函數(shù)動(dòng)態(tài)參數(shù)匹配及解包操作,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03django-rest-swagger的優(yōu)化使用方法
今天小編就為大家分享一篇django-rest-swagger的優(yōu)化使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08Python中低維數(shù)組填充高維數(shù)組的實(shí)現(xiàn)
今天小編就為大家分享一篇Python中低維數(shù)組填充高維數(shù)組的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12python學(xué)生管理系統(tǒng)的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了python學(xué)生管理系統(tǒng)的實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04淺談python內(nèi)置變量-reversed(seq)
下面小編就為大家?guī)?lái)一篇淺談python內(nèi)置變量-reversed(seq)。小編覺(jué)得挺不錯(cuò)的。現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06flask-socketio實(shí)現(xiàn)前后端實(shí)時(shí)通信的功能的示例
本文主要介紹了flask-socketio實(shí)現(xiàn)前后端實(shí)時(shí)通信的功能的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04使用scrapy ImagesPipeline爬取圖片資源的示例代碼
這篇文章主要介紹了使用scrapy ImagesPipeline爬取圖片資源的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09