diff --git a/djrill/mail/__init__.py b/djrill/mail/__init__.py new file mode 100644 index 0000000..960e87a --- /dev/null +++ b/djrill/mail/__init__.py @@ -0,0 +1,37 @@ +from django.core.exceptions import ImproperlyConfigured +from django.core.mail import EmailMultiAlternatives + + +class DjrillMessage(EmailMultiAlternatives): + alternative_subtype = "mandrill" + + def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, + connection=None, attachments=None, headers=None, alternatives=None, + cc=None, tags=None, track_opens=True, track_clicks=True): + + super(DjrillMessage, self).__init__(subject, body, from_email, to, bcc, + connection, attachments, headers, alternatives, cc) + + self.tags = self._set_mandrill_tags(tags) + self.track_opens = track_opens + self.track_clicks = track_clicks + + def _set_mandrill_tags(self, tags): + """ + Check that all tags are below 50 chars and that they do not start + with an underscore. + + Raise ImproperlyConfigured if an underscore tag is passed in to + alert the user. Any tag over 50 chars is left out of the list. + """ + tag_list = [] + + for tag in tags: + if len(tag) <= 50 and not tag.startswith("_"): + tag_list.append(tag) + elif tag.startswith("_"): + raise ImproperlyConfigured( + "Tags starting with an underscore are reserved for " + "internal use and will cause errors with Mandill's API") + + return tag_list diff --git a/djrill/mail/backends/__init__.py b/djrill/mail/backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djrill/mail/backends/djrill.py b/djrill/mail/backends/djrill.py new file mode 100644 index 0000000..1d05de6 --- /dev/null +++ b/djrill/mail/backends/djrill.py @@ -0,0 +1,131 @@ +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.core.mail.backends.base import BaseEmailBackend +from django.core.mail.message import sanitize_address +from django.utils import simplejson as json + +import requests + + +class DjrillBackend(BaseEmailBackend): + """ + Mandrill API Email Backend + """ + + def __init__(self, fail_silently=False, **kwargs): + """ + Set the API key, API url and set the action url. + """ + super(DjrillBackend, self).__init__(**kwargs) + self.api_key = getattr(settings, "MANDRILL_API_KEY", None) + self.api_url = getattr(settings, "MANDRILL_API_URL", None) + self.connection = None + + if not self.api_key: + raise ImproperlyConfigured("You have not set your mandrill api key " + "in the settings.py file.") + if not self.api_url: + raise ImproperlyConfigured("You have not added the Mandrill api " + "url to your settings.py") + + self.api_action = self.api_url + "/messages/send.json" + self.api_verify = self.api_url + "/users/verify-sender.json" + + def open(self, sender): + """ + """ + self.connection = None + + valid_sender = requests.post( + self.api_verify, data={"key": self.api_key, "email": sender}) + + if valid_sender.status_code == 200: + data = json.loads(valid_sender.content) + if data["is_enabled"]: + self.connection = True + return True + else: + if not self.fail_silently: + raise + + def send_messages(self, email_messages): + if not email_messages: + return + + num_sent = 0 + for message in email_messages: + self.open(message.from_email) + if not self.connection: + return + + sent = self._send(message) + + if sent: + num_sent += 1 + + return num_sent + + def _send(self, message): + if not message.recipients(): + return False + + self.sender = sanitize_address(message.from_email, message.encoding) + recipients_list = [sanitize_address(addr, message.encoding) + for addr in message.recipients()] + from email.utils import parseaddr + self.recipients = [{"email": e, "name": n} for n,e in [ + parseaddr(r) for r in recipients_list]] + + self.msg_dict = self._build_standard_message_dict(message) + + if getattr(message, "alternative_subtype", None): + if message.alternative_subtype == "mandrill": + if message.alternatives: + self._add_alternatives(message) + + djrill_it = requests.post(self.api_action, data=json.dumps({ + "key": self.api_key, + "message": self.msg_dict + })) + + if djrill_it.status_code != 200: + if not self.fail_silently: + raise + return False + return True + + def _build_standard_message_dict(self, message): + """ + Build standard message dict. + + Builds the standard dict that Django's send_mail and send_mass_mail + use by default. Standard text email messages sent through Django will + still work through Mandrill. + """ + return { + "text": message.body, + "subject": message.subject, + "from_email": self.sender, + "from_name": "Devs - FIX ME", + "to": self.recipients + } + + def _add_alternatives(self, message): + """ + There can be only one! ... alternative attachment. + + Since mandrill does not accept image attachments or anything other + than HTML, the assumption is the only thing you are attaching is + the HTML output for your email. + """ + if len(message.alternatives) > 1: + raise ImproperlyConfigured( + "Mandrill only accepts plain text and html emails. Please " + "check the alternatives you have attached to your message.") + + self.msg_dict.update({ + "html": message.alternatives[0][0], + "tags": message.tags, + "track_opens": message.track_opens, + "track_clicks": message.track_clicks + }) diff --git a/djrill/models.py b/djrill/models.py new file mode 100644 index 0000000..e69de29 diff --git a/djrill/tests.py b/djrill/tests.py index e69de29..70bd17b 100644 --- a/djrill/tests.py +++ b/djrill/tests.py @@ -0,0 +1,51 @@ +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.test import TestCase + +from djrill.mail import DjrillMessage + + +class DjrillMessageTests(TestCase): + def setUp(self): + self.subject = "Djrill baby djrill." + self.from_email = "test@example" + self.to = ["King Kong ", + "Cheetah