欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python編寫(xiě)郵件自動(dòng)發(fā)送工具的完整指南

 更新時(shí)間:2025年06月11日 09:30:50   作者:笨笨輕松熊  
自動(dòng)化郵件發(fā)送是一個(gè)非常實(shí)用的功能,無(wú)論是系統(tǒng)通知,營(yíng)銷郵件,還是日常工作報(bào)告,下面我們來(lái)看看Python如何使用smtplib實(shí)現(xiàn)郵件自動(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è)圖片

    今天小編就為大家分享一篇利用keras加載訓(xùn)練好的.H5文件,并實(shí)現(xiàn)預(yù)測(cè)圖片,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-01-01
  • python星號(hào)(*)和雙星號(hào)(**)?函數(shù)動(dòng)態(tài)參數(shù)匹配及解包操作方法

    python星號(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-03
  • django-rest-swagger的優(yōu)化使用方法

    django-rest-swagger的優(yōu)化使用方法

    今天小編就為大家分享一篇django-rest-swagger的優(yōu)化使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-08-08
  • Python中低維數(shù)組填充高維數(shù)組的實(shí)現(xiàn)

    Python中低維數(shù)組填充高維數(shù)組的實(shí)現(xiàn)

    今天小編就為大家分享一篇Python中低維數(shù)組填充高維數(shù)組的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-12-12
  • 使用python判斷jpeg圖片的完整性實(shí)例

    使用python判斷jpeg圖片的完整性實(shí)例

    今天小編就為大家分享一篇使用python判斷jpeg圖片的完整性實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-06-06
  • python學(xué)生管理系統(tǒng)的實(shí)現(xiàn)

    python學(xué)生管理系統(tǒng)的實(shí)現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了python學(xué)生管理系統(tǒng)的實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • 淺談python內(nèi)置變量-reversed(seq)

    淺談python內(nèi)置變量-reversed(seq)

    下面小編就為大家?guī)?lái)一篇淺談python內(nèi)置變量-reversed(seq)。小編覺(jué)得挺不錯(cuò)的。現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • 利用python檢測(cè)文本相似性的三種方法

    利用python檢測(cè)文本相似性的三種方法

    文本查重,也稱為文本去重,是一項(xiàng)旨在識(shí)別文本文檔之間的相似性或重復(fù)性的技術(shù)或任務(wù),它的主要目標(biāo)是確定一個(gè)文本文檔是否包含與其他文檔相似或重復(fù)的內(nèi)容,本文給大家介紹了利用python檢測(cè)文本相似性的原理和方法,需要的朋友可以參考下
    2023-11-11
  • flask-socketio實(shí)現(xiàn)前后端實(shí)時(shí)通信的功能的示例

    flask-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爬取圖片資源的示例代碼

    這篇文章主要介紹了使用scrapy ImagesPipeline爬取圖片資源的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評(píng)論