Scheduling Notifications in Django

Scheduling Notifications in Django

No cronjobs. No extra processes in your OS.
Just simple sending scheduled notifications.

Where I used this: Binge-o-Philia
For what: To send a reminder (notifications) of upcoming booked-movie.

rsz_1rsz_1movie_req_detail.png

TLDR; I maintain a table at my back-end server to store scheduled notifications. Then, I set-up a ping service. The pings act as a trigger at my back-end server to poll the DB for all scheduled notifications which lie within the following ping interval and send all of them.

notification-server

My first plan was to have a custom 'notification server' which would also act as a separate datastore for my booked-movie-reminders. I wrote the code for the notification server, which uses django-background-tasks at its core.

This is VERY simple to use, but it requires an extra (long-running) process, which again, was sort of having cron jobs. Also, having a separate (and dedicated) server also requires proper checks and a few other overheads at both the main back-end server and notification-server in case of revoked notifications (cancellation of booked movies).

ping-service

I started looking for options/alternatives if there is a solution already implemented (hopefully in a better way) and the concept of ping-services caught my attention.
What they basically do is to just hit a particular endpoint at regular (short) intervals, and some can even store the logs, responses, and even provide some insights from that data.
I tried a few and finally settled for Uptime-Doctor which was perfect for me, as it provides free service for a few endpoints and I had a need for only 3.

The Back-End

Since I wasn't going to have a separate server for storing the notifications' data, I needed a table for that in my back-end and made a simple model:

class MovieRequest(models.Model):

    creator = models.ForeignKey(User, ...)
    recipient = models.ForeignKey(User, ...)
    movie = models.ForeignKey(Movie, ...)
    date = models.DateField(null=False, blank=False)
    time = models.TimeField(null=False, blank=False)
    accepted = models.BooleanField(default=False)
    rejected = models.BooleanField(default=False)

class BookedMovieNotification(models.Model):

    movie_request = models.ForeignKey(MovieRequest, related_name='notifications',
                                      on_delete=models.CASCADE, null=False, blank=False)
    time = models.DateTimeField()

Then I reserved an endpoint for receiving pings, polling the DB and sending notifications.

from datetime import timedelta
from django.utils import timezone
from rest_framework.views import APIView
from .models import BookedMovieNotification

def send_booked_movie_reminder(notification: BookedMovieNotification) -> None:  
    ...

class BookedMovieReminderAPIView(APIView):  
    permission_classes = ()
    # Use some Auth to identify requests coming from ping-service

    def get(self, request, *args, **kwargs):
        poll_max_time = timezone.now() + timedelta(minutes=PING_INTERVAL_MINUTES)
        notifications = BookedMovieNotification.objects.filter(time__lte=poll_max_time)
        for notification in notifications:
            send_booked_movie_reminder(notification)
        return Response({})

Push Notifications

The app is made using React-Native and Expo and uses Expo's own Push-Notification features. The push-notifications from the python-based back-end was pretty straightforward as specified in exponent-server-sdk-python.

Conclusion

This approach feels like having a third-party cron job with easier set-up :/
So this may or may not be a good option depending on your use-case and securtity/dependency concerns. As I had to test the feature in minimal time, I went for it and worked out pretty well :)