79746125

Date: 2025-08-25 19:42:33
Score: 0.5
Natty:
Report link

You may also want to consider another approach of making the Djoser emails async by default.

The way I did this was to subclass Djoser's email classes and override the send() method so it uses a Celery task. The accepted solution works for one-off tasks, but this method makes sure there is consistency across all email types.


users/tasks.py

from django.core.mail import EmailMultiAlternatives
from celery import shared_task

@shared_task(bind=True, max_retries=3)
def send_email_task(self, subject, body, from_email, to, bcc=None, cc=None, reply_to=None, alternatives=None):
    try:
        email = EmailMultiAlternatives(
            subject=subject,
            body=body,
            from_email=from_email,
            to=to,
            bcc=bcc or [],
            cc=cc or [],
            reply_to=reply_to or []
        )
        if alternatives:
            for alt in alternatives:
                email.attach_alternative(*alt)
        email.send()
    except Exception as exc:
        raise self.retry(exc=exc, countdown=60)

This is a generic task that sends any Django email. Nothing here is Djoser-specific.


users/email.py

from django.conf import settings
from djoser import email
from .tasks import send_email_task


class AsyncDjoserEmailMessage(email.BaseDjoserEmail):
    """
    Override synchronous send to use Celery.
    """
    def send(self, to, fail_silently=False, **kwargs):
        self.render()
        self.to = to
        self.cc = kwargs.pop("cc", [])
        self.bcc = kwargs.pop("bcc", [])
        self.reply_to = kwargs.pop("reply_to", [])
        self.from_email = kwargs.pop("from_email", settings.DEFAULT_FROM_EMAIL)
        self.request = None  # don't pass request to Celery

        send_email_task.delay(
            subject=self.subject,
            body=self.body,
            from_email=self.from_email,
            to=self.to,
            bcc=self.bcc,
            cc=self.cc,
            reply_to=self.reply_to,
            alternatives=self.alternatives,
        )

Any email that inherits from this class will be sent asynchronously.


Now you can combine Djoser's built-in emails with your async base:

class PasswordResetEmail(email.PasswordResetEmail, AsyncDjoserEmailMessage):
    template_name = 'email/password_reset.html'

    def get_context_data(self):
        context = super().get_context_data()
        user = context.get('user')
        context['username'] = user.username
        context['reset_url'] = (
            f"{settings.FRONTEND_BASE_URL}/reset-password"
            f"?uid={context['uid']}&token={context['token']}"
        )
        return context


class ActivationEmail(email.ActivationEmail, AsyncDjoserEmailMessage):
    template_name = 'email/activation.html'

    def get_context_data(self):
        context = super().get_context_data()
        user = context.get('user')
        context['username'] = user.username
        context['verify_url'] = (
            f"{settings.FRONTEND_BASE_URL}/verify-email"
            f"?uid={context['uid']}&token={context['token']}"
        )
        return context


class ConfirmationEmail(email.ConfirmationEmail, AsyncDjoserEmailMessage):
    template_name = 'email/confirmation.html'

You can do the same for:

Each one gets async sending for free, and you can add extra context if you need it.


If you want to override Djoser's email, you need to make sure you add yours to the global templates dir so your templates get used instead. Examples (templates/email/...):

password_reset.html

{% block subject %}Reset your password on {{ site_name }}{% endblock %}

{% block text_body %}
Hello {{ username }}!

You requested a password reset for your account. Click the link below:

{{ reset_url }}
{% endblock %}

{% block html_body %}
<h2>Hello {{ username }}!</h2>
<p>Click the link to reset:</p>
<a href="{{ reset_url }}">Reset Password</a>
{% endblock %}

activation.html

{% block subject %}Verify your email for {{ site_name }}{% endblock %}

{% block text_body %}
Hello {{ username }}, please verify your email:
{{ verify_url }}
{% endblock %}

{% block html_body %}
<h2>Hello {{ username }}!</h2>
<p><a href="{{ verify_url }}">Verify Email</a></p>
{% endblock %}

...and similarly for confirmation.html.

Make sure your settings.py points at the template folder:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        ...
    }
]

Add Djoser URLs:

urlpatterns = [
    path("users/", include("djoser.urls")),
    ...
]

Start Celery:

celery -A config worker -l info

(replace config with your project name)

Trigger a Djoser action (e.g. reset_password or activation) and you'll see Celery run send_email_task.


This way, all Djoser emails in which you inherit AsyncDjoserEmailMessage() become async, not just password reset.

Reasons:
  • Blacklisted phrase (1): the link below
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Winifred Igboama