You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Python为自动发送的账单提醒邮件添加付款单附件?

Fixing Email Attachment Issue in Your Billing Reminder Script

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/except block around the server code to catch any sending errors (like wrong password or SMTP issues)
  • The MIME type application/octet-stream works for most file types; if you're sending a specific file (like PDF), you can use application/pdf instead for better client support

内容的提问来源于stack exchange,提问作者Guvv

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 15:17:42