pmclanahan / django-celery-email

A Django email backend that uses a celery task for sending the email.
BSD 3-Clause "New" or "Revised" License
477 stars 110 forks source link

Attachments: TypeError("a bytes-like object is required, not 'str'", #45

Closed sidmitra closed 7 years ago

sidmitra commented 7 years ago

I get the following error when i try to attach files.

[2017-07-18 05:15:38,369: WARNING/PoolWorker-4] djcelery_email_send_multiple[c7cebd25-27d8-4b3c-9734-0a6a5729ee4f]: Failed to send email message to ['foobar@example.com'], retrying. (TypeError("a bytes-like object is required, not 'str'",))

I've tried a number of ways to attach files. But essentially it's this

docfile = default_storage.open(attachment['file_path'], 'r')
if docfile:
    message.attach(attachment['name'], docfile.read())

I have to do all the default_storage stuff because my files live on S3 and I need to open it via the storage and then call read(). But i don't think it matters what storage i use. Also docfile.read() returns a bytes string. Note i'm using python 3.6+ and version 2.0.0 of the module.

It seems calling attach() converts it to str. After some debugging, it seems to happen here https://github.com/django/django/blob/master/django/core/mail/message.py#L324

It seems here we're expecting contents to be binary(does that mean a bytes string?)

filename, binary_contents, mimetype = attachment
contents = base64.b64encode(binary_contents).decode('ascii')

This is where i get the TypeError. Note my attached files are txt.

There could probably be something obvious i'm missing here, on how to attach files. I will explore some more(and try other files) and add some more info later. But any help would be greatly appreciated.

sidmitra commented 7 years ago

I ended up using MimeBase instance instead of the list [name, content, mimetype] here's an example if someone else comes across this.

    # Attach files
    for attachment in attachments:
        name = attachment['name']  
        file_path = attachment['file_path']   # relative path 
        docfile = default_storage.open(file_path, 'rb')  # if using S3 then we need to open the file using the same storage as it was saved with(in my case default_storage is S3Boto)
        if docfile:
            part = MIMEBase('application', 'octet-stream')
            part.set_payload(docfile.read())
            part.add_header(
                'Content-Disposition',
                'attachment; filename="{0}"'.format(name))
            message.attach(part)
pmac commented 7 years ago

Thanks for the followup!