pythondjangoemailbulk-email

Send bulk emails in Django with the same attachment


I want to send emails to members of my site who are to attend a meeting (ie. guests), each with (the same) PDF attachment. I'm doing this with Django's built-in bulk email functionality, in connection.send_messages(messages). At the moment I'm doing this:

guests = Guest.objects.all()
connection = mail.get_connection()
connection.open()
messages = []
for guest in guests:
    msg = EmailMultiAlternatives(title, text_content, from_address, [guest.email], connection=connection)
    msg.attach_alternative(html_content, 'text/html')
    pdf_data = open(os.path.join(settings.MEDIA_ROOT, 'uploads/flyer.pdf'))
    msg.attach('Invitation Card.pdf', pdf_data.read(), 'application/pdf')
    pdf_data.close()
    messages.append(msg)
connection.send_messages(messages)
connection.close()

Now, when I do it like this, the same PDF file will be loaded for every email, attached separately, and then sent separately for each email, as if it were different PDFs. If the file is 10MB, that 10MB will be uploaded to my mail server for every single guest, where it could have been only once.

So the question is: Is it possible to attach a file to all emails at once, thereby also only uploading it once? Or am I just plain doing it wrong?

UPDATE:

If I change the attach line to the following:

msg.attach_file(os.path.join(settings.MEDIA_ROOT, 'uploads/flyer.pdf'))

would that solve my problem?


Solution

  • Looking at django/core/mail/message.py reveals attach_file is merely a convenience function that opens the file for you before calling attach:

    def attach_file(self, path, mimetype=None):
        """Attaches a file from the filesystem."""
        filename = os.path.basename(path)
        content = open(path, 'rb').read()
        self.attach(filename, content, mimetype)
    

    You could avoid opening the attachment and reading it into memory over and over again if you subclass EmailMultiAlternatives and override the attach method. You should look into using a job/task queue for this like celery.