django-tasks-sqs
An Amazon SQS backend and worker for Django's built-in
tasks framework (django.tasks, Django 6.0+).
Django defines how you declare and enqueue background tasks, but ships no production
backend and no worker. This package provides both, on top of SQS. Your code only uses
the standard django.tasks API, so you can switch backends later without touching it.
from django.tasks import task
@task
def send_welcome_email(user_id: int) -> None:
...
send_welcome_email.enqueue(user_id=42) # returns immediately; a worker runs it
Install
pip install django-tasks-sqs
# settings.py
INSTALLED_APPS = [..., "django_tasks_sqs"]
TASKS = {
"default": {
"BACKEND": "django_tasks_sqs.SQSBackend",
"QUEUES": ["default", "emails"],
"OPTIONS": {
"region_name": "eu-west-1",
# SQS queue = prefix + django queue name ("myapp-default", "myapp-emails")...
"queue_name_prefix": "myapp-",
# ...or map queue names to URLs explicitly:
# "queue_urls": {"default": "https://sqs.eu-west-1.amazonaws.com/123456789012/app"},
},
}
}
Then run one or more workers:
python manage.py sqs_worker # all queues of the "default" backend
python manage.py sqs_worker --queue emails --concurrency 4
The queues must already exist. Create them with your usual infrastructure tooling (Terraform, CDK, CloudFormation…).
Features
- Standard
django.tasksAPI:@task,.enqueue(),.aenqueue(),.using(),takes_context, async tasks, and thetask_enqueued/task_started/task_finishedsignals. - Deferred tasks:
task.using(run_after=...). Delays up to 15 minutes use SQSDelaySeconds. Longer delays are re-queued by the worker until they are due. - Retries and dead-letter queues: a failed task is not deleted, so SQS delivers it
again. Add a redrive policy to the queue to move it to a DLQ after
maxReceiveCountattempts.--retry-backoff Nadds exponential backoff between attempts. - Long-running tasks: a heartbeat extends the message's visibility timeout while the task runs, so another worker doesn't pick it up halfway through.
- Graceful shutdown: on
SIGTERM/SIGINTthe worker finishes its current tasks and exits. Plays well with ECS, Kubernetes and systemd. - FIFO queues (queue names ending in
.fifo). - Typed, with 100% test coverage. Tested against Django 6.0 and 6.1 on Python 3.12–3.14.
How it works
web process SQS queue sqs_worker
─────────── ───────── ──────────
task.enqueue(args) ──JSON msg──▶ [ ... ] ──long poll──▶ import task by path
│
delete ◀── success ────┤
visibility timeout expires ◀── failure ────┘ (retry / DLQ)
1. Enqueueing
task.enqueue(*args, **kwargs) runs in your web process. SQSBackend:
- validates the task, as
django.tasksrequires: module-level function, JSON-serialisable arguments, a queue listed inQUEUES…; - resolves the SQS queue URL, either from
queue_urlsor by callingGetQueueUrlwithqueue_name_prefix + queue_name(the result is cached); - sends one message and returns a
TaskResultwith statusREADY.
The message body is a small, versioned JSON envelope. Only the task's import path travels, never code or pickles:
{"v": 1, "id": "…", "task": "myapp.tasks.send_welcome_email",
"args": [], "kwargs": {"user_id": 42}, "queue_name": "default",
"backend": "default", "enqueued_at": "2026-09-25T10:00:00+00:00", "run_after": null}
The task path is also sent as a message attribute (task), which is handy for
filtering and debugging in the AWS console.
2. Deferring (run_after)
SQS can delay a message for at most 15 minutes (DelaySeconds). For longer delays the
backend sends the message with the maximum delay. When a worker receives it before
run_after, it sends a new copy delayed again and deletes the original. This repeats
until the task is due, so a task can be deferred for any length of time.
3. Consuming
manage.py sqs_worker starts --concurrency threads per queue. Each thread loops:
- Long-polls SQS (
ReceiveMessagewithWaitTimeSeconds=20), which is cheap when the queue is idle. - Imports the task by path. If the message is malformed or the task can't be imported, it is logged and left alone, so the redrive policy eventually moves it to the DLQ.
- Runs it exactly like Django's
ImmediateBackenddoes: it builds aTaskResult, sendstask_started, calls the function (sync or async, withTaskContextiftakes_context=True), sendstask_finished, and closes stale DB connections before and after. - Acknowledges or retries. On success the message is deleted. On failure it is
kept: SQS delivers it again when the visibility timeout expires, or after
--retry-backoffseconds (doubling each attempt) if you set it.
While a task runs, a heartbeat thread calls ChangeMessageVisibility every half
timeout, so a slow task is never handed to a second worker.
4. Shutting down
SIGTERM/SIGINT sets a stop flag. Threads finish the task they are running, stop
polling and exit. A message that was received but not finished simply becomes visible
again, and another worker picks it up.
Code map
| Module | What lives there |
|---|---|
backend.py |
SQSBackend: settings, boto3 client, queue URL resolution, enqueue, system checks |
message.py |
TaskMessage: the JSON envelope and its validation |
worker.py |
Worker: polling threads, execution, retries, heartbeat, deferral |
management/commands/sqs_worker.py |
CLI flags and signal handling |
Worker options
| Flag | Default | |
|---|---|---|
--backend |
default |
Alias in settings.TASKS |
--queue |
all QUEUES |
Repeat to consume several |
--concurrency |
1 | Polling threads per queue, each running one task at a time |
--wait-time |
20 | Long-poll seconds (max 20) |
--max-messages |
1 | Messages per receive (1–10). Keep it low for slow tasks |
--visibility-timeout |
queue's | Override for received messages |
--retry-backoff |
off | Base seconds for exponential backoff: base * 2**(attempt-1) |
--no-heartbeat |
Don't extend visibility while tasks run |
You can also run a worker from code with django_tasks_sqs.Worker.
Things to know
- Delivery is at least once. That is how SQS works: a task can run more than once, for example if a worker dies after running it but before deleting the message. Make tasks idempotent.
- No result storage (yet).
supports_get_result = False, sotask.get_result(id)raisesNotImplementedError. Store results yourself if you need them. - No priorities. SQS has none. Use separate queues and give the important ones more workers.
- FIFO queues don't support
run_after, because SQS has no per-message delay on FIFO queues. - Messages are limited to 256 KB. Pass IDs, not big payloads.
TaskContext.attemptcomes from SQS'sApproximateReceiveCount.- IAM permissions: the web process needs
sqs:SendMessageandsqs:GetQueueUrl. Workers also needsqs:ReceiveMessage,sqs:DeleteMessage,sqs:ChangeMessageVisibilityandsqs:GetQueueAttributes.
Local development
Point endpoint_url at LocalStack or
moto in server mode:
"OPTIONS": {"endpoint_url": "http://localhost:4566", "region_name": "us-east-1", ...}
For unit tests, use Django's ImmediateBackend instead, which runs tasks inline.
Roadmap
Ideas where help is very welcome. Open an issue to discuss before starting something big:
- Optional result storage (e.g. in the Django database), so
get_result()works - Batch sends (
SendMessageBatch) for enqueueing many tasks at once - Priorities emulated with several queues and weighted polling
- Health check and metrics hooks for the worker (Prometheus / CloudWatch)
- Payloads over 256 KB stored in S3 (extended client pattern)
- Integration tests against LocalStack in CI
Contributing
Contributions of any size are welcome: bug reports, docs, tests, features. Start with CONTRIBUTING.md. In short:
git clone https://github.com/mgarcia0094/django-tasks-sqs && cd django-tasks-sqs
uv sync # install with dev dependencies
uv run pytest # tests (SQS is mocked with moto, no AWS account needed)
uv run ruff check . # lint
uv run mypy # strict type checking
Please follow the Code of Conduct. To report a security issue, see SECURITY.md.
License
Release files for django-tasks-sqs 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_tasks_sqs-0.1.0.tar.gz | 20.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_tasks_sqs-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.9 kB
Release files / django_tasks_sqs-0.1.0.tar.gz
| Download URL | django_tasks_sqs-0.1.0.tar.gz |
|---|---|
| Size | 20.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
de2232d0c0348ee35b0a840f75a4990caa1dfa241935c77fee5f9c6be1f90f40
|
|
BLAKE2b-256 checksum How to use checksums |
743b665626bc588e9a4eeb5de34099a43e1588eff26f5b429b28c7b799ebf744
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / django_tasks_sqs-0.1.0-py3-none-any.whl
| Download URL | django_tasks_sqs-0.1.0-py3-none-any.whl |
|---|---|
| Size | 16.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1cef9988c1f2929768ae4ce36ffbdd99acf92a549868cff77a65f4016a81d536
|
|
BLAKE2b-256 checksum How to use checksums |
895c87b61d81d14090e5fee861ee1498b5d611cdbdf906fa600d82fb1398bf22
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log