Martin Massera
Martin Massera

Reputation: 1906

django - persisting an email to database to be able to send it later?

In my django project I want to store an email in the database to be able to retrieve it later and send it. I'm implementing a throttling mechanism for sending emails to many users.

I thought it would be as easy as storing 'to, from, subject, body' but then I realized there are attachments, multipart emails, etc, there is at least two classes EmailMessage and EmailMultiAlternatives... too many variables and options!

I thought of storing the raw email that one gets with the message() method, but then not sure how to construct the email back. Also tried pickle but got can't pickle lock objects.

Any ideas?

Upvotes: 0

Views: 1660

Answers (1)

rrauenza
rrauenza

Reputation: 6983

I'm going to make a guess based on your can't pickle lock objects message that you may be trying to pickle your objects with the default SMTP connection included.

Try it without it - grepping through the source it is the smtp module that has a self._lock. Pass connection=None to the constructor of the messages.

On Django 1.9, this works for me (Python2):

from django.core.mail import EmailMessage
from django.core.mail import EmailMultiAlternatives
import pickle

email = EmailMessage(
    'Hello',
    'Body goes here',
    '[email protected]',
    ['[email protected]', '[email protected]'],
    ['[email protected]'],
    reply_to=['[email protected]'],
    headers={'Message-ID': 'foo'},
    connection=None,
)

email.attach("foo", [l for l in open(__file__)], 'text')

print len(pickle.dumps(email))

subject, from_email, to = 'hello', '[email protected]', '[email protected]'
text_content = 'This is an important message.'
html_content = '<p>This is an <strong>important</strong> message.</p>'
msg = EmailMultiAlternatives(subject, text_content, from_email, [to], connection=None)
msg.attach_alternative(html_content, "text/html")

print len(pickle.dumps(msg))

According to the Django code in messages.py, later when you call send() it will attempt to use get_connection() from django.core.mail to get your default connection if EmailMessage.connection is None.

....Alternatively, if you want to use json, this also worked with connection=None:

import json                                                            
print json.dumps(msg.__dict__)                                         
print json.dumps(email.__dict__)

This means you could fairly easily write a JSONEncoder and JSONDecoder to serialize/deserialize your objects as well by basically using the __dict__ of the object.

More on JSON:

As I showed above, encoding the __dict__ makes the encoding easy. You could do msg.__dict__ = json.load(...), but what makes it difficult is the EmailMessage object must be created before you change its values. So you could initialize msg with an EmailMessage that has dummy values, and then assign the __dict__, or decode the JSON and construct the object explicitly by passing the arguments (...and functions) stored in the JSON to the constructor. This requires you to know the internals, though.

I'd go with pickle, although there are security implications.

This SO question covers some other alternatives as well.

Upvotes: 1

Related Questions