I want to have an email templates stored in my Django database so that admins can manage them without me changing the code. The requirement is to have HTML-emails support as well as an ability to send files attached. Neither standard Django send_mail nor other builtin features don’t support that feature so below you can find my solution to that problem.

The snippet below relies to the Django’s EmailMultiAlternatives and basically wraps it into a class.

Model definition

The model provides capabilities to store the email template in the database to allow admins to manage these templates easily.

subject *- An email subject.

*- An email subject. template_key * - A unique identifier of the email template that could be referenced from the code.

* - A unique identifier of the email template that could be referenced from the code. to_email - Optional. A default email that the template will be sent to. Can be used to send email to admins.

- Optional. A default email that the template will be sent to. Can be used to send email to admins. from_email - Optional. A default email that the template will be sent from. If not specified, settings.DEFAULT_FROM_EMAIL is used.

- Optional. A default email that the template will be sent from. If not specified, is used. html_template and plain_text - Body of the email in text and html formats.

from django import template from django.conf import settings from django.core.mail import send_mail, EmailMultiAlternatives from django.db import models from django.template import Context class EmailTemplate (models . Model): """ Email templates get stored in database so that admins can change emails on the fly """ subject = models . CharField(max_length = 255 , blank = True, null = True) to_email = models . CharField(max_length = 255 , blank = True, null = True) from_email = models . CharField(max_length = 255 , blank = True, null = True) html_template = models . TextField(blank = True, null = True) plain_text = models . TextField(blank = True, null = True) is_html = models . BooleanField(default = False) is_text = models . BooleanField(default = False) # unique identifier of the email template template_key = models . CharField(max_length = 255 , unique = True) def get_rendered_template (self, tpl, context): return self . get_template(tpl) . render(context) def get_template (self, tpl): return template . Template(tpl) def get_subject (self, subject, context): return subject or self . get_rendered_template(self . subject, context) def get_body (self, body, context): return body or self . get_rendered_template(self . _get_body(), context) def get_sender (self): return self . from_email or settings . DEFAULT_FROM_EMAIL def get_recipient (self, emails, context): return emails or [self . get_rendered_template(self . to_email, context)] @staticmethod def send ( * args, ** kwargs): EmailTemplate . _send( * args, ** kwargs) @staticmethod def _send (template_key, context, subject = None, body = None, sender = None, emails = None, bcc = None, attachments = None): mail_template = EmailTemplate . objects . get(template_key = template_key) context = Context(context) subject = mail_template . get_subject(subject, context) body = mail_template . get_body(body, context) sender = sender or mail_template . get_sender() emails = mail_template . get_recipient(emails, context) if mail_template . is_text: return send_mail(subject, body, sender, emails, fail_silently = not settings . DEBUG) msg = EmailMultiAlternatives(subject, body, sender, emails, alternatives = ((body, 'text/html' ),), bcc = bcc ) if attachments: for name, content, mimetype in attachments: msg . attach(name, content, mimetype) return msg . send(fail_silently = not (settings . DEBUG or settings . TEST)) def _get_body (self): if self . is_text: return self . plain_text return self . html_template def __str__(self): return "<{}> {}" . format(self . template_key, self . subject)

Example usage

Sending a notification to the admin about new expense request from an employee.

EmailTemplate . send( 'expense_notification_to_admin' , { # context object that email template will be rendered with 'expense' : expense_request, })

Or sending an invoice to the customer.

invoice_pdf = invoice . pdf_file . pdf_file_data send_email( # a string such as 'invoice_to_customer' template_name, # context that contains Customer, Invoice and other objects ctx, # list of receivers i.e. ['customer1@example.com', 'customer2@example.com] emails = emails, # attached PDF file of the invoice attachments = [(invoice . reference, invoice_pdf, 'application/pdf' )] )

Running as a celery task

Emails can be sent via celery by adding a task to tasks.py :

from celery import task from core.models import EmailTemplate @task def send_email ( * args, ** kwargs): return EmailTemplate . send( * args, ** kwargs)

Enabling in an admin panel

admin.py can look like follows: