pythonzipsmtplib

python mailing script - adding zip


I would like to ask for an advice for adding a zip option to the mailing script I am using for the delivery of reports.

I have email attachment limit set to 25MB and therefore some reports in json format that exceeds 25MB are dropped by the mailer script. I wanted to add a zip support that will compress the attachment if that is bigger than for ex. 15MB.

Below code is mailer part only.

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.utils import COMMASPACE
import sys
from os.path import basename
import argparse
import conf
import os
import zipfile
import zlib

###############################################################################
# Mailer function for server running environment

def send_mail(recipients, cc=None, subject=None, attachment=None, body=None, send_from=None): 
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = send_from
    msg['To'] = ','.join(recipients)
    if cc:
        msg['CC'] = ','.join(cc)
        recipients.extend(cc)
    if body:
        text = MIMEText(body, 'html')
        msg.attach(text)
    if attachment:
        with open(attachment, 'rb') as fp:
            att = MIMEApplication(fp.read(), Name=basename(attachment))
        att['Content-Disposition'] = f'attachment; filename="{basename(attachment)}"'
        msg.attach(att)

    smtp = smtplib.SMTP(conf.RELAY)
    smtp.sendmail(send_from, recipients, msg.as_string())
    smtp.quit()


Solution

  • Assuming the code you already have correctly attaches arbitrary attachments, including binary ones, then the following should zip up an attchment that is > 15M. If the original file's base name was, for example, test.dat, then the attachment archive name will be test.dat.zip and will contain test.dat as the single compressed file implicitly using zlib. No temporary files are needed for this as an in-memory buffer is used. So if the attachment is not outrageously large, this should be quite efficient:

        if attachment:
            MAX_ATTACHMENT_SIZE = 15 * 1024 * 1024
            with open(attachment, 'rb') as fp:
                bytes = fp.read()
            attachment_name = basename(attachment)
            if len(bytes) > MAX_ATTACHMENT_SIZE:
                from io import BytesIO
                buffer = BytesIO()
                with zipfile.ZipFile(buffer, 'w', compression=zipfile.ZIP_DEFLATED) as zip_file:
                    zip_file.writestr(attachment_name, bytes)
                # New attachment name and new data:
                attachment_name += '.zip'
                bytes = buffer.getvalue()            
            att = MIMEApplication(bytes, Name=attachment_name)
            att['Content-Disposition'] = f'attachment; filename="{attachment_name}"'
            msg.attach(att)