Dev recipient for when you need to test the content/design of your e-mails.
E-mail log is useful if en e-mail gets lost, or you need to check how many are being sent or so.
I don't log any html/mime parts (html content or files), only text message and filenames. But you can add it if you really need it.
First we need an EmailLog model
from django.utils.translation import gettext_lazy as _
from django.db import models
class EmailLog(models.Model):
ctime = models.DateTimeField(null=False, auto_now_add=True, verbose_name=_('Sent time'))
sender = models.EmailField(null=False, blank=True, default='', verbose_name=_('From'))
to = models.JSONField(null=False, blank=True, default=list)
cc = models.JSONField(null=False, blank=True, default=list)
bcc = models.JSONField(null=False, blank=True, default=list)
reply_to = models.JSONField(null=False, blank=True, default=list)
subject = models.CharField(null=False, max_length=1024)
body = models.TextField(null=False, blank=True, default='')
attachments = models.JSONField(null=False, blank=True, default=list)
status = models.BooleanField(null=False, default=True, verbose_name=_('Sent'))
exception = models.TextField(null=False, blank=True, default='')
traceback = models.TextField(null=False, blank=True, default='')
def __str__(self):
return self.subject
Of course we need to run makemigrations and migrate!
Then we add it to admin.py
from django.contrib import admin
from .models import EmailLog
class EmailLogAdmin(admin.ModelAdmin):
list_display = ("ctime", "subject", "status")
actions = []
list_filter = ["status"]
search_fields = ["subject", "to", "body"]
def has_add_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
admin.site.register(EmailLog, EmailLogAdmin)
Then create a mail.py in your app with the new SMTP backend
import smtplib
import traceback
from email.mime.base import MIMEBase
from django.apps import apps
from django.utils.functional import cached_property
from django.core.mail.backends.smtp import EmailBackend
from django.conf import settings
class ImprovedSMTPEmailBackend(EmailBackend):
"""
uses EmailLog model for logging all outgoing e-mails
if you use fail_silently=True and the connection fails, there will not be any log
"""
@cached_property
def email_log_model(self):
return apps.get_model(settings.EMAIL_LOG_MODEL)
def _send(self, email_message):
fail_silently = self.fail_silently
self.fail_silently = False
log_object = self.email_log_model(
sender=email_message.from_email,
to=email_message.to,
cc=email_message.cc,
bcc=email_message.bcc,
reply_to=email_message.reply_to,
subject=email_message.subject,
body=email_message.body,
)
attachments = []
for attachment in email_message.attachments:
if isinstance(attachment, tuple):
attachments.append(attachment[0])
elif isinstance(attachment, MIMEBase):
# TODO one day when it's needed
attachments.append('MIMEBase attachment (getting filename not implemented)')
log_object.attachments = attachments
# on dev server we usually want to send all e-mails to system@company.com or something
reset_recipients = False
if (dev_recipient := getattr(settings, "EMAIL_DEV_RECIPIENTS", None)) and email_message.recipients():
reset_recipients = True # we need to reset it back, so this condition works next time too
original_recipients = email_message.recipients
email_message.recipients = lambda *args, **kwargs: [dev_recipient] if isinstance(dev_recipient, str) else dev_recipient
try:
super_response = super()._send(email_message)
except smtplib.SMTPException as e:
log_object.status = False
log_object.exception=str(e)
log_object.traceback=traceback.format_exc()
if not fail_silently:
log_object.save()
raise
else:
super_response = None
except Exception as e:
log_object.status = False
log_object.exception=str(e)
log_object.traceback=traceback.format_exc()
log_object.save()
raise
finally:
if reset_recipients:
email_message.recipients = original_recipients
self.fail_silently = fail_silently
if not super_response:
log_object.status = False
log_object.save()
return super_response
And finally edit your settings.py
EMAIL_BACKEND = "your_app.mail.ImprovedSMTPEmailBackend"
if DEBUG:
# EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" # when you don't want your e-mails to be sent at all
EMAIL_LOG_MODEL = "your_app.EmailLog"
EMAIL_DEV_RECIPIENTS = ["your.email@example.com", "your.project.manager@example.com"] # set your e-mail addresses
See? Easy peasy 🙂