├── requirements.txt ├── MANIFEST.in ├── django_q_email ├── __init__.py ├── utils.py └── backends.py ├── .gitignore ├── AUTHORS.md ├── setup.py ├── CHANGES.md ├── LICENSE └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | django-q 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include *.md 3 | -------------------------------------------------------------------------------- /django_q_email/__init__.py: -------------------------------------------------------------------------------- 1 | from . import backends 2 | 3 | 4 | __version__ = '5.0.1' 5 | 6 | 7 | __all__ = ['__version__', backends] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Deployment files 2 | *.egg-info 3 | dist 4 | 5 | # Environment files 6 | site-packages 7 | build 8 | env 9 | .cache 10 | *.py[cod] 11 | 12 | # OS-specific files 13 | .DS_Store 14 | Desktop.ini 15 | Thumbs.db 16 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | [Django Q Email][home] is written and maintained by Joe Esposito, 5 | along with the following contributors: 6 | 7 | - Ankit ([@ankitch](https://github.com/ankitch)) 8 | 9 | 10 | [home]: README.md 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | 5 | def read(filename): 6 | with open(os.path.join(os.path.dirname(__file__), filename)) as f: 7 | return f.read() 8 | 9 | 10 | setup( 11 | name='django-q-email', 12 | version='5.0.1', 13 | description='Queues the sending of email with Django Q.', 14 | long_description=__doc__, 15 | author='Joe Esposito', 16 | author_email='joe@joeyespo.com', 17 | url='http://github.com/joeyespo/django-q-email', 18 | license='MIT', 19 | platforms='any', 20 | packages=find_packages(), 21 | install_requires=read('requirements.txt').splitlines(), 22 | zip_safe=False, 23 | ) 24 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Django Q Email Changelog 2 | ------------------------ 3 | 4 | 5 | #### Version 5.0.1 (2019-11-28) 6 | 7 | - Fix configuration and set `DJANGO_Q_EMAIL_USE_DICTS` to `True` by default 8 | 9 | 10 | ### Version 5.0.0 (2019-11-28) 11 | 12 | - Enqueue human-readable objects by default instead of pickled objects (set `DJANGO_Q_EMAIL_USE_DICTS` to `False` to disable) 13 | 14 | 15 | #### Version 4.1.0 (2019-08-11) 16 | 17 | - Import `EMAIL_ERROR_HANDLER` instead of calling it directly 18 | 19 | 20 | ### Version 4.0.0 (2019-07-25) 21 | 22 | - Add `DJANGO_Q_EMAIL_ERROR_HANDLER` to handle errors 23 | 24 | 25 | ### Version 3.0.0 (2019-05-26) 26 | 27 | - **Python 3.7+ support** 28 | 29 | - Fix installation steps in README 30 | 31 | 32 | ### Version 2.0.0 (2018-09-04) 33 | 34 | - **Django Q 1.0+ support** 35 | 36 | - Fix typo in README ([#1](https://github.com/joeyespo/django-q-email/pull/1) - thanks, [@ankitch][]!) 37 | 38 | 39 | ### Version 1.0.0 (2017-02-09) 40 | 41 | - First public release 42 | 43 | 44 | [@ankitch]: https://github.com/ankitch 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Joe Esposito 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /django_q_email/utils.py: -------------------------------------------------------------------------------- 1 | from django.core.mail.message import EmailMessage, EmailMultiAlternatives 2 | 3 | 4 | def to_dict(email_message): 5 | """ 6 | Converts the specified email message to a dictionary representation. 7 | """ 8 | if type(email_message) not in [EmailMessage, EmailMultiAlternatives]: 9 | return email_message 10 | email_message_data = { 11 | 'subject': email_message.subject, 12 | 'body': email_message.body, 13 | 'from_email': email_message.from_email, 14 | 'to': email_message.to, 15 | 'bcc': email_message.bcc, 16 | 'attachments': email_message.attachments, 17 | 'headers': email_message.extra_headers, 18 | 'cc': None, 19 | 'reply_to': None, 20 | } 21 | if isinstance(email_message, EmailMultiAlternatives): 22 | email_message_data['alternatives'] = email_message.alternatives 23 | return email_message_data 24 | 25 | 26 | def from_dict(email_message_data): 27 | """ 28 | Creates an EmailMessage or EmailMultiAlternatives instance from the 29 | specified dictionary. 30 | """ 31 | kwargs = dict(email_message_data) 32 | alternatives = kwargs.pop('alternatives', None) 33 | return ( 34 | EmailMessage(**kwargs) if not alternatives else 35 | EmailMultiAlternatives(alternatives=alternatives, **kwargs)) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Django Q Email 2 | ============== 3 | 4 | [![Current version on PyPI](http://img.shields.io/pypi/v/django-q-email.svg)][pypi] 5 | 6 | `django-q-email` is a reusable Django app for queuing the sending of email with [Django Q][]. 7 | 8 | 9 | Installation 10 | ------------ 11 | 12 | Install the latest version with pip: 13 | 14 | ```bash 15 | $ pip install django-q-email 16 | ``` 17 | 18 | Then in `settings.py`: 19 | 20 | ```python 21 | EMAIL_BACKEND = 'django_q_email.backends.DjangoQBackend' 22 | ``` 23 | 24 | Then send email in the normal way, as per the [Django email docs](https://docs.djangoproject.com/en/1.10/topics/email/), 25 | and they will be sent in a background task. See Django Q for more information](https://github.com/Koed00/django-q). 26 | 27 | 28 | Configuration 29 | ------------- 30 | 31 | `DJANGO_Q_EMAIL_BACKEND` - Backend used in the background task (default: `django.core.mail.backends.smtp.EmailBackend`) 32 | `DJANGO_Q_EMAIL_USE_DICTS` - Store Python dictionaries instead of pickled `EmailMessage` and `EmailMultiAlternatives` (default: `True`) 33 | `DJANGO_Q_EMAIL_ERROR_HANDLER` - Optional function to be called if sending fails (called as `DJANGO_Q_EMAIL_ERROR_HANDLER(email_message, exception)`) 34 | 35 | 36 | Requirements 37 | ------------ 38 | 39 | - [Django](https://www.djangoproject.com/) >= 1.8 40 | - [Django Q](https://github.com/Koed00/django-q) 41 | 42 | 43 | Contributing 44 | ------------ 45 | 46 | 1. Check the open issues or open a new issue to start a discussion around 47 | your feature idea or the bug you found 48 | 2. Fork the repository and make your changes 49 | 3. Create a new pull request 50 | 51 | 52 | [pypi]: http://pypi.python.org/pypi/django-q-email/ 53 | [django q]: https://github.com/Koed00/django-q 54 | -------------------------------------------------------------------------------- /django_q_email/backends.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.mail import get_connection 3 | from django.core.mail.backends.base import BaseEmailBackend 4 | from django.utils.module_loading import import_string 5 | 6 | from .utils import from_dict, to_dict 7 | 8 | try: 9 | from django_q.tasks import async_task 10 | except ImportError: 11 | # Django Q < 1.0 12 | from django_q import tasks 13 | # Use getattr to avoid SyntaxError: invalid syntax on Python 3.7+ 14 | async_task = getattr(tasks, 'async') 15 | 16 | 17 | DEFAULT_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 18 | EMAIL_BACKEND = getattr(settings, 'DJANGO_Q_EMAIL_BACKEND', DEFAULT_BACKEND) 19 | EMAIL_ERROR_HANDLER = getattr(settings, 'DJANGO_Q_EMAIL_ERROR_HANDLER', None) 20 | DJANGO_Q_EMAIL_USE_DICTS = getattr(settings, 'DJANGO_Q_EMAIL_USE_DICTS', True) 21 | 22 | 23 | class DjangoQBackend(BaseEmailBackend): 24 | use_dicts = DJANGO_Q_EMAIL_USE_DICTS 25 | 26 | def send_messages(self, email_messages): 27 | num_sent = 0 28 | for email_message in email_messages: 29 | if self.use_dicts: 30 | email_message = to_dict(email_message) 31 | async_task('django_q_email.backends.send_message', email_message) 32 | num_sent += 1 33 | return num_sent 34 | 35 | 36 | def send_message(email_message): 37 | """ 38 | Sends the specified email synchronously. 39 | 40 | See DjangoQBackend for sending in the background. 41 | """ 42 | try: 43 | if isinstance(email_message, dict): 44 | email_message = from_dict(email_message) 45 | connection = email_message.connection 46 | email_message.connection = get_connection(backend=EMAIL_BACKEND) 47 | try: 48 | email_message.send() 49 | finally: 50 | email_message.connection = connection 51 | except Exception as ex: 52 | if not EMAIL_ERROR_HANDLER: 53 | raise 54 | email_error_handler = import_string(EMAIL_ERROR_HANDLER) 55 | email_error_handler(email_message, ex) 56 | --------------------------------------------------------------------------------