Python編寫郵件自動發(fā)送工具的完整指南
自動化郵件發(fā)送是一個非常實用的功能。無論是系統(tǒng)通知、營銷郵件、還是日常工作報告,Python的smtplib庫都能幫助我們輕松實現(xiàn)這些功能。本教程將詳細介紹如何使用Python的smtplib庫開發(fā)一個功能完整的郵件自動發(fā)送工具。
代碼實現(xiàn)與知識點解析
1. 導入必要的庫
# 導入tkinter模塊,用于創(chuàng)建圖形界面 import tkinter as tk # 導入ttk模塊,用于創(chuàng)建標簽頁控件 from tkinter import ttk, filedialog, messagebox, scrolledtext # 導入os模塊,用于文件操作 import os # 導入csv模塊,用于讀取CSV文件 import csv # 導入datetime模塊,用于獲取當前日期和時間 from datetime import datetime # 導入logging模塊,用于日志記錄 import logging # 導入sys模塊,用于系統(tǒng)操作 import sys
2. 配置郵件服務器參數(shù)
SMTP(Simple Mail Transfer Protocol)是一種簡單的郵件傳輸協(xié)議,用于在服務器之間發(fā)送電子郵件。在Python中,我們可以使用smtplib庫來實現(xiàn)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' # 郵件服務器默認發(fā)件人
注意:授權碼不是密碼
知識點:
- SMTP服務器地址和端口:不同郵箱服務商有不同的服務器地址和端口
- TLS加密:保護郵件傳輸安全
- 應用專用密碼:許多郵箱服務商(如QQ郵箱)要求使用應用專用密碼而非登錄密碼
3. 創(chuàng)建郵件發(fā)送類
- 日志
- 發(fā)送郵件(單個收件人)
- 發(fā)送郵件(批量)
def create_email_sender(self):
"""創(chuàng)建臨時配置對象"""
# 創(chuàng)建一個臨時的配置對象
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"
# 如果日志目錄不存在,則創(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(
# 設置日志級別
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):
"""發(fā)送郵件(單個收件人)"""
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 = []
# 創(chuàng)建郵件對象
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)
# 發(fā)送郵件
server.sendmail(self.default_sender, all_recipients, msg.as_string())
# 關閉連接
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')
# 如果收件人郵箱地址為空,則跳過此條記錄
if not to_addr:
self.logger.warning("缺少收件人郵箱地址,跳過此條記錄")
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實例
config = TempConfig(self)
return CustomEmailSender(config)4. 實現(xiàn)郵件發(fā)送功能
簡單郵件發(fā)送
def send_simple_email(self):
"""發(fā)送簡單郵件"""
try:
# 使用當前配置創(chuàng)建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("正在發(fā)送郵件...")
self.update_idletasks()
if sender.send_email(to_addr, subject, body):
messagebox.showinfo("成功", "郵件發(fā)送成功!")
self.status_var.set("郵件發(fā)送成功")
else:
messagebox.showerror("錯誤", "郵件發(fā)送失敗,請查看日志獲取詳細信息。")
self.status_var.set("郵件發(fā)送失敗")
except Exception as e:
messagebox.showerror("錯誤", f"發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"發(fā)送郵件時出錯: {str(e)}")
HTML郵件
def send_html_email(self):
"""發(fā)送HTML郵件"""
try:
# 使用當前配置創(chuàng)建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("正在發(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("錯誤", "郵件發(fā)送失敗,請查看日志獲取詳細信息。")
self.status_var.set("郵件發(fā)送失敗")
except Exception as e:
messagebox.showerror("錯誤", f"發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"發(fā)送郵件時出錯: {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:
# 使用當前配置創(chuàng)建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("正在發(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("錯誤", "郵件發(fā)送失敗,請查看日志獲取詳細信息。")
self.status_var.set("郵件發(fā)送失敗")
except Exception as e:
messagebox.showerror("錯誤", f"發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"發(fā)送郵件時出錯: {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:
# 使用當前配置創(chuàng)建EmailSender實例
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文件、主題模板和正文模板為空,則提示錯誤
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("正在批量發(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("錯誤", f"批量發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"批量發(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
)
# 將配置數(shù)據(jù)轉換為字典
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:
# 如果無法加載配置文件,則返回默認配置
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創(chuàng)建包含多部分內容的郵件
文件操作:讀取附件文件并添加到郵件中
異常處理:捕獲并記錄郵件發(fā)送過程中的錯誤
上下文管理器:使用with語句自動關閉SMTP連接
5. 實現(xiàn)視圖
def create_simple_email_tab(self):
"""創(chuàng)建簡單郵件標簽頁"""
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)
# 發(fā)送按鈕
ttk.Button(tab, text="發(fā)送", 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):
"""創(chuàng)建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)
# 發(fā)送按鈕
ttk.Button(tab, text="發(fā)送", 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):
"""創(chuàng)建帶附件郵件標簽頁"""
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)
# 設置權重
tab.columnconfigure(1, weight=1)
tab.rowconfigure(2, weight=1)
tab.rowconfigure(3, weight=1)
def create_bulk_email_tab(self):
"""創(chuàng)建批量發(fā)送郵件標簽頁"""
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)
# 設置權重
tab.columnconfigure(1, weight=1)
tab.rowconfigure(2, weight=1)
def create_config_tab(self):
"""創(chuàng)建配置標簽頁"""
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)
# 默認發(fā)件人
ttk.Label(tab, text="默認發(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)
# 測試連接按鈕
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)
常用布局參數(shù)解釋
1.創(chuàng)建參數(shù):
- tab: 父容器(與標簽相同)
- textvariable=self.simple_to_var: 綁定到前面創(chuàng)建的字符串變量
- width=50: 輸入框寬度為50個字符單位
2.布局參數(shù) (.grid):
- row=0: 放在網(wǎng)格的第0行
- column=0: 放在網(wǎng)格的第0列
- sticky=tk.W: 標簽在網(wǎng)格單元格內靠左對齊(西側)
- padx=5: 水平方向(左右)各5像素的外邊距
- pady=5: 垂直方向(上下)各5像素的外邊距
6.總代碼
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Python郵件自動發(fā)送工具 - 圖形界面版
功能:使用tkinter實現(xiàn)郵件發(fā)送圖形界面,支持文本郵件、HTML郵件、帶附件郵件和批量發(fā)送
"""
# 導入tkinter模塊,用于創(chuàng)建圖形界面
import tkinter as tk
# 導入ttk模塊,用于創(chuàng)建標簽頁控件
from tkinter import ttk, filedialog, messagebox, scrolledtext
# 導入os模塊,用于文件操作
import os
# 導入csv模塊,用于讀取CSV文件
import csv
# 導入datetime模塊,用于獲取當前日期和時間
from datetime import datetime
# 導入logging模塊,用于日志記錄
import logging
# 導入sys模塊,用于系統(tǒng)操作
import sys
class EmailSenderApp(tk.Tk):
def __init__(self):
super().__init__()
# 設置窗口標題
self.title("Python郵件自動發(fā)送工具")
# 設置窗口大小
self.geometry("800x600")
# 設置窗口是否可調整大小
self.resizable(True, True)
# 加載配置
self.mail_config = self.load_config()
# 創(chuàng)建標簽頁控件
self.notebook = ttk.Notebook(self)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 創(chuàng)建各個標簽頁
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()
# 設置狀態(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文件中導入配置
from config import (
MAIL_SERVER, MAIL_PORT, MAIL_USE_TLS,
MAIL_USERNAME, MAIL_PASSWORD, MAIL_DEFAULT_SENDER
)
# 將配置數(shù)據(jù)轉換為字典
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:
# 如果無法加載配置文件,則返回默認配置
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):
"""創(chuàng)建簡單郵件標簽頁"""
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)
# 發(fā)送按鈕
ttk.Button(tab, text="發(fā)送", 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):
"""創(chuàng)建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)
# 發(fā)送按鈕
ttk.Button(tab, text="發(fā)送", 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):
"""創(chuàng)建帶附件郵件標簽頁"""
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)
# 設置權重
tab.columnconfigure(1, weight=1)
tab.rowconfigure(2, weight=1)
tab.rowconfigure(3, weight=1)
def create_bulk_email_tab(self):
"""創(chuàng)建批量發(fā)送郵件標簽頁"""
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)
# 設置權重
tab.columnconfigure(1, weight=1)
tab.rowconfigure(2, weight=1)
def create_config_tab(self):
"""創(chuàng)建配置標簽頁"""
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)
# 默認發(fā)件人
ttk.Label(tab, text="默認發(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)
# 測試連接按鈕
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):
"""發(fā)送簡單郵件"""
try:
# 使用當前配置創(chuàng)建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("正在發(fā)送郵件...")
self.update_idletasks()
if sender.send_email(to_addr, subject, body):
messagebox.showinfo("成功", "郵件發(fā)送成功!")
self.status_var.set("郵件發(fā)送成功")
else:
messagebox.showerror("錯誤", "郵件發(fā)送失敗,請查看日志獲取詳細信息。")
self.status_var.set("郵件發(fā)送失敗")
except Exception as e:
messagebox.showerror("錯誤", f"發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"發(fā)送郵件時出錯: {str(e)}")
def send_html_email(self):
"""發(fā)送HTML郵件"""
try:
# 使用當前配置創(chuàng)建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("正在發(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("錯誤", "郵件發(fā)送失敗,請查看日志獲取詳細信息。")
self.status_var.set("郵件發(fā)送失敗")
except Exception as e:
messagebox.showerror("錯誤", f"發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"發(fā)送郵件時出錯: {str(e)}")
def send_attachment_email(self):
"""發(fā)送帶附件的郵件"""
try:
# 使用當前配置創(chuàng)建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("正在發(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("錯誤", "郵件發(fā)送失敗,請查看日志獲取詳細信息。")
self.status_var.set("郵件發(fā)送失敗")
except Exception as e:
messagebox.showerror("錯誤", f"發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"發(fā)送郵件時出錯: {str(e)}")
def send_bulk_emails(self):
"""批量發(fā)送郵件"""
try:
# 使用當前配置創(chuàng)建EmailSender實例
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文件、主題模板和正文模板為空,則提示錯誤
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("正在批量發(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("錯誤", f"批量發(fā)送郵件時出錯: {str(e)}")
self.status_var.set(f"批量發(fā)送郵件時出錯: {str(e)}")
def create_email_sender(self):
"""創(chuàng)建臨時配置對象"""
# 創(chuàng)建一個臨時的配置對象
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"
# 如果日志目錄不存在,則創(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(
# 設置日志級別
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):
"""發(fā)送郵件(單個收件人)"""
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 = []
# 創(chuàng)建郵件對象
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)
# 發(fā)送郵件
server.sendmail(self.default_sender, all_recipients, msg.as_string())
# 關閉連接
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')
# 如果收件人郵箱地址為空,則跳過此條記錄
if not to_addr:
self.logger.warning("缺少收件人郵箱地址,跳過此條記錄")
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實例
config = TempConfig(self)
return CustomEmailSender(config)
if __name__ == "__main__":
# 創(chuàng)建EmailSenderApp實例
app = EmailSenderApp()
# 啟動主循環(huán)
app.mainloop()
7.效果圖

總結
通過本教程,我們學習了如何使用Python的smtplib庫開發(fā)一個功能完整的郵件自動發(fā)送工具。主要涵蓋了以下知識點:
- SMTP協(xié)議基礎知識
- 使用smtplib連接郵件服務器
- 創(chuàng)建和發(fā)送不同類型的郵件(文本、HTML、附件)
- 批量發(fā)送個性化郵件
- 異常處理和日志記錄
- 從配置文件加載設置
這個郵件發(fā)送工具可以應用于多種場景,如系統(tǒng)通知、營銷郵件、報表自動發(fā)送等。通過進一步擴展,還可以實現(xiàn)更復雜的功能,滿足不同的業(yè)務需求。
以上就是Python編寫郵件自動發(fā)送工具的完整指南的詳細內容,更多關于Python郵件自動發(fā)送的資料請關注腳本之家其它相關文章!
相關文章
利用keras加載訓練好的.H5文件,并實現(xiàn)預測圖片
今天小編就為大家分享一篇利用keras加載訓練好的.H5文件,并實現(xiàn)預測圖片,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01
python星號(*)和雙星號(**)?函數(shù)動態(tài)參數(shù)匹配及解包操作方法
這篇文章主要介紹了python星號(*)和雙星號(**)?函數(shù)動態(tài)參數(shù)匹配及解包操作,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
django-rest-swagger的優(yōu)化使用方法
今天小編就為大家分享一篇django-rest-swagger的優(yōu)化使用方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08
Python中低維數(shù)組填充高維數(shù)組的實現(xiàn)
今天小編就為大家分享一篇Python中低維數(shù)組填充高維數(shù)組的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12
flask-socketio實現(xiàn)前后端實時通信的功能的示例
本文主要介紹了flask-socketio實現(xiàn)前后端實時通信的功能的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04
使用scrapy ImagesPipeline爬取圖片資源的示例代碼
這篇文章主要介紹了使用scrapy ImagesPipeline爬取圖片資源的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09

