
Runway GWM-1: The First General World Model That Simulates Reality at 24fps — 3D Worlds, Avatars, and Robotics
March 16, 2026
NVIDIA GTC 2026 Keynote Recap: Vera Rubin Platform, 336B Transistors, HBM4, and Why the CPU Is Taking Center Stage
March 16, 2026Stop installing Celery, Redis, and a message broker just to send a confirmation email. Django 6.0, released January 15, 2026, ships with something developers have begged for since 2008: a built-in Tasks framework that handles background jobs natively. After 18 years and countless third-party workarounds, the Django 6.0 tasks framework finally acknowledges that not every project needs a distributed task queue to process a PDF.
What the Django 6.0 Tasks Framework Actually Does
The new django.tasks module introduces two core primitives: the @task decorator and the .enqueue() method. Together, they let you offload work outside the HTTP request-response cycle without importing a single third-party package. Here is the simplest possible example:
from django.tasks import task
@task
def send_welcome_email(user_id):
user = User.objects.get(id=user_id)
send_mail("Welcome!", "Thanks for signing up.", "noreply@example.com", [user.email])
# In your view
def register(request):
user = User.objects.create(...)
send_welcome_email.enqueue(user_id=user.id)
return HttpResponse("Registration complete!")
The @task decorator marks any function as a background task. Calling .enqueue() pushes it to whatever backend you have configured. The view returns immediately — the user does not wait for the email to send. This pattern covers 80% of the use cases that previously required Celery: sending emails, generating reports, processing uploads, and updating caches.

Two Built-In Backends (And Why You Need a Third)
Django 6.0 ships with two backends out of the box, and neither is what you expect:
- ImmediateBackend (default) — Runs tasks synchronously on the same thread. Your view blocks until the task finishes. This is the development default, useful for debugging but not for production.
- DummyBackend — Stores tasks without executing them. Perfect for testing.
This was the most controversial decision in the Django 6.0 release. The core team intentionally did not ship a database-backed worker. Their reasoning: task execution infrastructure varies wildly between projects, and forcing one approach would repeat the mistakes of other frameworks. Instead, Django provides the interface and lets the ecosystem build the backends.
django-tasks-local: Zero-Infrastructure Background Processing
The community responded immediately. django-tasks-local provides a ThreadPoolExecutor-based backend that actually runs tasks in background threads:
# settings.py
TASKS = {
"default": {
"BACKEND": "django_tasks_local.ThreadPoolBackend",
"OPTIONS": {
"MAX_WORKERS": 10,
"MAX_RESULTS": 1000,
}
}
}
With this configuration, .enqueue() pushes tasks to a thread pool. Your request thread returns immediately, and the task executes in a separate worker thread. No Redis, no RabbitMQ, no separate process to manage. The tradeoff: results live in memory only — if Django restarts, pending tasks are lost.
Django 6.0 Tasks Framework vs Celery: When to Use Which
This is not a Celery killer. The Django 6.0 tasks framework targets a different audience entirely. Here is the decision matrix:
Use Django Tasks when: You need to send emails asynchronously, process small file uploads, update search indexes, run webhook callbacks, or build MVPs without infrastructure overhead. If your background work is “fire and forget” with no complex retry logic, Django Tasks is the right choice.
Stay with Celery when: You need periodic/scheduled tasks (cron-like), complex task chains and workflows, guaranteed delivery with database-backed persistence, distributed processing across multiple workers, or retry policies with exponential backoff. Celery’s mature ecosystem handles enterprise-grade scenarios that Django Tasks deliberately avoids.

Beyond Tasks: Other Django 6.0 Features Worth Knowing
The Tasks framework is the headline, but Django 6.0 ships with several other significant additions:
- Native Content Security Policy (CSP) — The new
ContentSecurityPolicyMiddlewarelets you configure CSP headers through Python dictionaries instead of third-party packages. Cross-site scripting protection is now a settings change, not a dependency. - Template Partials — The
{% partialdef %}and{% partial %}tags enable component-based template design. You can define and reuse named fragments within a single template file, eliminating the need for dozens of tiny include files. - Modern Email API — Django adopts Python’s modern
emailmodule, replacing legacy email handling with cleaner, more maintainable code. - AsyncPaginator — New
AsyncPaginatorandAsyncPageclasses bring first-class async support to Django’s pagination system. - Python 3.12-3.14 Support — Django 6.0 drops Python 3.10 and 3.11, requiring Python 3.12 or newer.
Setting Up Django 6.0 Tasks: A Step-by-Step Tutorial
Let me walk you through a practical setup. We will build a simple notification system that sends emails and generates PDF reports in the background.
Step 1: Install Django 6.0 and django-tasks-local
pip install django==6.0.3 django-tasks-local
Step 2: Configure the backend in settings.py
INSTALLED_APPS = [
...
"django_tasks_local",
]
TASKS = {
"default": {
"BACKEND": "django_tasks_local.ThreadPoolBackend",
"OPTIONS": {"MAX_WORKERS": 5}
}
}
Step 3: Define your tasks
# tasks.py
from django.tasks import task
from django.core.mail import send_mail
@task
def send_notification(user_id, message):
user = User.objects.get(id=user_id)
send_mail("Notification", message, "noreply@app.com", [user.email])
@task
def generate_report(report_id):
report = Report.objects.get(id=report_id)
pdf = render_to_pdf(report.template, report.data)
report.file.save(f"report_{report_id}.pdf", ContentFile(pdf))
report.status = "completed"
report.save()
Step 4: Enqueue from your views
# views.py
def create_report(request):
report = Report.objects.create(user=request.user, status="pending")
generate_report.enqueue(report_id=report.id)
send_notification.enqueue(
user_id=request.user.id,
message="Your report is being generated."
)
return JsonResponse({"status": "processing", "report_id": report.id})
That is the entire setup. No Redis server, no Celery worker process, no message broker configuration. The view returns instantly while both tasks execute in background threads.
The Bigger Picture: Django’s Philosophy Shift
Django 6.0’s approach to background tasks reflects a broader philosophy shift in the Python web ecosystem. Instead of building a monolithic solution, the core team created a clean interface (django.tasks) and delegated implementation to the community. This mirrors how Django handles caching (multiple backends), sessions (multiple backends), and file storage (multiple backends). The pattern works: you write your task code once, swap backends as your project grows, and never rewrite business logic.
For most Django projects — especially those that currently install Celery just to send emails asynchronously — the built-in Tasks framework is a genuine game-changer. Start with django-tasks-local for development and low-traffic production, then migrate to a database-backed or Redis-backed backend when scale demands it. The migration is a single settings change.
Whether you are modernizing a Django monolith or architecting a new Python backend from scratch, the right infrastructure decisions save months of debugging. If you need guidance on backend architecture, task queue design, or automation systems, let’s talk.



