如何使用Python为自动发送的账单提醒邮件添加付款单附件?
Hey there! Let's get that payment slip attachment working properly. The root of your problem is that askopenfilename() only returns a file path string, and you're attaching it as plain text with MIMEText—that's why recipients only see the filename instead of the actual file. To send a proper attachment, we need to read the file's binary content and wrap it in the correct MIME object.
Step 1: Add Required Imports
First, add these modules to handle file attachments:
from email.mime.base import MIMEBase from email import encoders import os
Step 2: Fix the Attachment Logic
Replace your current attachment line (msg.attach(MIMEText(askopenfilename()))) with this code, which reads the file content and attaches it properly:
# Let user select the payment file file_path = askopenfilename() if file_path: # Ensure a file was selected # Extract just the filename (without full path) for the email header filename = os.path.basename(file_path) # Open the file in binary mode to read its content with open(file_path, "rb") as attachment: # Create a base MIME object for binary files part = MIMEBase("application", "octet-stream") part.set_payload(attachment.read()) # Encode the file content to Base64 (required for email transmission) encoders.encode_base64(part) # Set headers to mark this as an attachment part.add_header( "Content-Disposition", f"attachment; filename= {filename}", ) # Attach the file to your email message msg.attach(part)
Bonus: Fix the Month Variable Bug
I noticed another small issue: your month variable only holds the last value from the spreadsheet loop, so all emails will show the same month. To fix this, update how you store data in unpaidMembers:
# Inside the payment check loop unpaidMembers[client] = (email, month) # Store email and month as a tuple
Then adjust the email sending loop to use this tuple:
for client, (email, month) in unpaidMembers.items(): body = f"Client: {client} | Month: {month}" print(f'Sending email to {email}...') # Rest of your email setup code follows
Full Modified Code
Here's the complete script with all fixes applied:
import openpyxl, smtplib, sys, os from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase from email import encoders from tkinter import messagebox from tkinter.filedialog import askopenfilename # Opens the spreadsheet and obtains the status of the last payment. wb = openpyxl.load_workbook('C:/temp/cobranca.xlsx') sheet = wb['Sheet1'] lastCol = sheet.max_column # Checks the payment status of each customer. unpaidMembers = {} for r in range(2, sheet.max_row + 1): for c in range(3, lastCol + 1): payment = sheet.cell(row=r, column=c).value if payment != 'ok': client = sheet.cell(row=r, column=1).value email = sheet.cell(row=r, column=2).value month = sheet.cell(row=1, column=c).value unpaidMembers[client] = (email, month) # Store email + month print(f'Row: {r} | Column: {c} | Client: {client} | Email: {email} | Month: {month}') # Log in to your email account and send emails for client, (email, month) in unpaidMembers.items(): body = f"Client: {client} | Month: {month}" print(f'Sending email to {email}...') # Create message object instance msg = MIMEMultipart() # Setup message parameters password = "password" msg['From'] = "email@email.com" msg['To'] = email msg['Subject'] = f"{client} - Open honorary." # Add message body msg.attach(MIMEText(body, 'plain')) # Handle attachment file_path = askopenfilename() if file_path: filename = os.path.basename(file_path) with open(file_path, "rb") as attachment: part = MIMEBase("application", "octet-stream") part.set_payload(attachment.read()) encoders.encode_base64(part) part.add_header( "Content-Disposition", f"attachment; filename= {filename}", ) msg.attach(part) # Create server and send email try: server = smtplib.SMTP('mail.omnia.net.br', 587) server.starttls() server.login(msg['From'], password) server.sendmail(msg['From'], email, msg.as_string()) server.quit() print(f"Successfully sent email to {email}") except Exception as e: print(f"Failed to send email to {email}: {str(e)}") messagebox.showinfo("System Message", "Reminders sent successfully.")
A couple extra notes:
- I added a
try/exceptblock around the server code to catch any sending errors (like wrong password or SMTP issues) - The MIME type
application/octet-streamworks for most file types; if you're sending a specific file (like PDF), you can useapplication/pdfinstead for better client support
内容的提问来源于stack exchange,提问作者Guvv

