Django cookbook · Intermediate

Run background work with Django's tasks framework

Sending emails or generating reports inside requests makes them slow and fragile.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Define tasks with @task (Django 6's built-in tasks framework) and enqueue them after the transaction commits. Configure a real backend (such as the database backend from the django-tasks package, or a Celery/RQ setup) in production; the built-in ImmediateBackend runs tasks inline for development and tests.

Solution

# reports/tasks.py
from django.core.mail import send_mail
from django.tasks import task


@task(queue_name="emails")
def send_welcome_email(user_id: int) -> None:
    from django.contrib.auth import get_user_model

    user = get_user_model().objects.get(id=user_id)      # pass IDs, load fresh data in the task
    send_mail("Welcome!", "Thanks for signing up.", None, [user.email])


# accounts/views.py
from functools import partial

from django.db import transaction


def complete_signup(user):
    transaction.on_commit(partial(send_welcome_email.enqueue, user_id=user.id))


# settings.py
TASKS = {
    "default": {"BACKEND": "django.tasks.backends.immediate.ImmediateBackend", "QUEUES": ["default", "emails"]},
}

Pitfalls

  • Passing model instances to tasks; they're serialized and stale when the task runs. Pass IDs.
  • Enqueuing inside a transaction that later rolls back, so the task runs for data that doesn't exist.
  • Non-idempotent tasks; workers may retry, so make them safe to run twice.

Variations

  • Celery remains common for complex workflows (chains, retries with backoff, beat schedules).
  • Use .using(priority=...) or separate queues for slow and fast work.

Further reading: Django tasks framework

Reference: https://docs.djangoproject.com/en/6.0/topics/tasks/

More Django recipes

Was this page helpful?