utils/mail.py

class SendmailQueue, which instance is available globally as EmailQueue, allows to send multiple HTML emails with attachments. In case sendmail error is occured, error message can be converted to form non-field errors with form named argument of .flush() method (works with AJAX and non-AJAX forms):

from django_jinja_knockout.utils.mail import EmailQueue

EmailQueue.add(
    subject='Thank you for registration at our site!',
    html_body=body,
    to=destination_emails,
).flush(
    form=self.form
)

When there is no form submitted or it’s undesirable to add form’s non-field error, request named argument of .flush() may be supplied instead. It also works with both AJAX and non-AJAX views. AJAX views would use client-side Client-side viewmodels and AJAX response routing, displaying error messages in BootstrapDialog window. Non-AJAX views would use Django messaging framework to display sendmail errors:

from django_jinja_knockout.utils.mail import EmailQueue

EmailQueue.add(
    subject='Thank you for registration at our site!',
    html_body=body,
    to=destination_emails,
).flush(
    request=self.request
)

SendmailQueue class functionality could be extended by injecting ioc class. It allows to use database backend or non-SQL store to process emails in background, for example as Celery task. SendmailQueue class .add() and .flush() methods could be overridden in self.ioc and new methods can be added as well.

uncaught_exception_email function can be used to monkey patch Django exception BaseHandler to use SendmailQueue to send the uncaught exception reports to selected email addresses.

Here is the example of extending EmailQueue instance of SendmailQueue via custom ioc class (EmailQueueIoc) and monkey patching Django exception BaseHandler. This code should be placed in the project’s apps.py:

class MyAppConfig(AppConfig):
    name = 'my_app'
    verbose_name = "Verbose name of my application"

    def ready(self):
        from django_jinja_knockout.utils.mail import EmailQueue
        # EmailQueueIoc should have custom .add() and / or .flush() methods implemented.
        # Original .add() / .flush() methods may be called via ._add() / ._flush().
        from my_app.tasks import EmailQueueIoc

        EmailQueueIoc(EmailQueue)

        # Save uncaught exception handler.
        BaseHandler.original_handle_uncaught_exception = BaseHandler.handle_uncaught_exception
        # Override uncaught exception handler.
        BaseHandler.handle_uncaught_exception = uncaught_exception_email
        BaseHandler.developers_emails = ['user@host.org']
        BaseHandler.uncaught_exception_subject = 'Django exception stack trace for my project'

my_app.tasks.py:

class EmailQueueIoc:

    def __init__(self, email_queue):
        self.queue = email_queue
        self.instances = []
        # Maximum count of messages to send in one batch.
        self.batch_limit = 10
        self.max_total_errors = 3
        email_queue.set_ioc(self)

    def add(self, **kwargs):
        # Insert your code here.
        # Call original _add():
        return self.queue._add(**kwargs)

    def flush(self, **kwargs):
        # Insert your code here.
        # Call original _flush():
        return self.queue._flush(**kwargs)

    def celery_task():
        # Insert your code here.

@app.task
def email_send_batch():
    EmailQueue.celery_task()