DEV Community

Cover image for ⚙️ Task Scheduling in Django: A Practical Guide with Real Use Cases
Bharat Solanke
Bharat Solanke

Posted on

2

⚙️ Task Scheduling in Django: A Practical Guide with Real Use Cases

Image description

In most web applications, there comes a time when you need to automate things — send an email later, run a report every night, clean up data weekly, or notify users just before a deadline. As a Django developer, I faced this exact situation in a recent project and had to choose between two popular Python tools: Celery and APScheduler.
While both are great in their own right, they serve different purposes. This blog isn’t just another comparison table — it’s a walk-through of a real use case that required both scheduled and background tasks, and how I decided which tool to use, with code snippets and reasoning.💡

🧩 The Use Case

In my Django project, I had two core automation requirements:
📨 Send reminder emails to users if they had pending tasks for the day.
Convert ongoing events to follow-ups automatically if a shift was about to end in the next 5 minutes.
These needed to run on a schedule, handle potential scaling in the future, and importantly, not block the main application thread. So I evaluated Celery and APScheduler.

⚙️ What is Celery?

Celery is a distributed task queue system that can run asynchronous or scheduled jobs using worker processes. It’s powerful, production-grade, and ideal for tasks that need to run outside the request/response cycle — such as sending emails or heavy data processing.
Key Features:
🔄 Asynchronous background tasks
📦 Works with message brokers like Redis or RabbitMQ
✅ Retry support, error handling, monitoring
🗓️ Celery Beat for scheduled jobs

⏱️ What is APScheduler?

APScheduler (Advanced Python Scheduler) is a lightweight, in-process scheduler that supports cron-like scheduling, interval-based tasks, and more. It’s perfect for small apps or periodic jobs that don’t need distributed workers.

Key Features:

⏰ Cron and interval-based scheduling
⚙️ Runs in the same Python process
🧘 Minimal setup
💾 Persistent job stores optional

💭 Why I Chose Celery for My Use Case

1. 📨 Reminder Emails for Pending Tasks

Sending emails — especially when users number in the hundreds or thousands — is not something you want to do in-process. It could slow down your app and lead to timeouts. Celery allowed me to queue the email task and let a separate worker handle it

# tasks.py
from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_reminder_email(user_email):
    send_mail(
        subject='Reminder: You have pending tasks',
        message='Please complete your tasks before shift ends.',
        from_email='admin@example.com',
        recipient_list=[user_email],
    )
Enter fullscreen mode Exit fullscreen mode

And using Celery Beat, I scheduled this check every hour:

# settings.py
from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {
    'check-pending-tasks': {
        'task': 'myapp.tasks.check_and_send_reminders',
        'schedule': crontab(minute=0, hour='*'),
    },
}
Enter fullscreen mode Exit fullscreen mode

2. 🔁 Converting Events to Follow-ups Before Shift End

This task had to run every minute, checking whether any shift is ending in the next 5 minutes. For this, Celery again made sense — especially since I wanted the logic to scale in the future.

@shared_task
def convert_events_to_followups():
    now = timezone.now()
    upcoming_shifts = Shift.objects.filter(end_time__lte=now + timedelta(minutes=5))

    for shift in upcoming_shifts:
        events = Event.objects.filter(shift=shift, status='open')
        for event in events:
            FollowUp.objects.create(event=event)
            event.status = 'converted'
            event.save()
Enter fullscreen mode Exit fullscreen mode

🧪 When I Would Use APScheduler Instead

Let’s say I just needed a simple task that logs something or clears expired sessions every night. I could easily use APScheduler without setting up Redis or workers.

Example:

# scheduler.py
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger

def clear_expired():
    print("Clearing expired sessions...")

scheduler = BackgroundScheduler()
scheduler.add_job(clear_expired, CronTrigger(hour=0, minute=0))
scheduler.start()
Enter fullscreen mode Exit fullscreen mode

That’s it — no workers, no message broker, no external process. Just Python code. 🧑‍💻

🧠 Final Thoughts

Choosing between Celery and APScheduler isn’t about which is better — it’s about what your project needs. 🎯
In my case, where I needed background processing, email sending, task retries, and scaling — Celery was a no-brainer. 💪
But if you're working on a small Django app and just want to run something every midnight — APScheduler is your lightweight friend. 🧸
Start simple. Scale when needed. Pick the right tool for the right task.

Tiger Data image

🐯 🚀 Timescale is now TigerData

Building the modern PostgreSQL for the analytical and agentic era.

Read more

Top comments (0)

AWS Q Developer image

Build your favorite retro game with Amazon Q Developer CLI in the Challenge & win a T-shirt!

Feeling nostalgic? Build Games Challenge is your chance to recreate your favorite retro arcade style game using Amazon Q Developer’s agentic coding experience in the command line interface, Q Developer CLI.

Participate Now

👋 Kindness is contagious

Explore this insightful piece, celebrated by the caring DEV Community. Programmers from all walks of life are invited to contribute and expand our shared wisdom.

A simple "thank you" can make someone’s day—leave your kudos in the comments below!

On DEV, spreading knowledge paves the way and fortifies our camaraderie. Found this helpful? A brief note of appreciation to the author truly matters.

Let’s Go!