Search code examples
pythonemailpython-3.xmime-typesmime

Zip sent via email is invalid on first attempt


I try to create a zip archive containing all files from a specific directory(and subdirectories) and send it via mail:

#Create archive containing all files from directory "reports/"
zipf = zipfile.ZipFile('reports.zip', 'w')
for root, dirs, files in os.walk('reports/'):
for file in files:
    zipf.write(os.path.join(root, file))

#Create email
msg = MIMEMultipart()
msg["From"] = emailfrom
msg["To"] = emailto
msg["Subject"] = "Monatliche Reports - Verrechnung an Kunden"

#Attach report.zip to email
fp = open(fileToSend, "rb")
attachment = MIMEBase('application', 'zip')
attachment.set_payload(fp.read())
fp.close()
encoders.encode_base64(attachment)
attachment.add_header("Content-Disposition", "attachment",
filename=fileToSend)
msg.attach(attachment)

#Send email via localhost smtp-server
server = smtplib.SMTP("localhost")
server.sendmail(emailfrom, emailto, msg.as_string())
server.quit()

The script seems to work. I recive the mail including the attached zip archive containing all the files. When the script is executed there are 2 possible situations:

Case 1: there is already a zip archive before executing the script, named report.zip (old one from the last run)

Case 2: there is no zip archive before executing the script.

In case 1 everything works fine. The old one is replaced with the newly generated and will then be sent via email.

In case 2 the report.zip is generated and sent via email, but it is invalid. If I try to open it on Windows with 7zip(or the windows onboard tools) it just say "the archive is invalid". I found out that only the report.zip sent via email is broken. If I send the report.zip generated in Case 2 by hand via email I can unzip it and use the files.

I'm a python newbie and to be honest it was hard enough to get to the point where I am at the moment, but solving this problem headaches me. Can anyone explain me what I'm doing wrong ?


Solution

  • You need to close the zip file after adding the files to ensure the archive is complete.

    https://docs.python.org/2/library/zipfile.html#zipfile.ZipFile.close

    better yet use a with statement :

    with zipfile.ZipFile('reports.zip', 'w') as zipf: 
       for root, dirs, files in os.walk('reports/'):
          for file in files:
             zipf.write(os.path.join(root, file))    
    

    Details As I understand them.

    1st run:

    • zipfile creates a file descriptor (in memory placeholder) and a package manifest (files to add)
    • zipfile.write() compresses files into the file descriptor and adds the file to the manifest
    • msg.attach() the file hasn't flushed so there is nothing at location still in memory.
    • --End script file descriptor clean up flushes file descriptor to disk. ( reports.zip exists but is incomplete/invalid )

    2nd run:

    • zipfile creates a fd and manifest
    • zipfile.write() compresses files to the fd and adds the file to the manifest
    • msg.attach() the file had flushed last time but is still incomplete Attaches invalid file.