GSoC 2025: Centralized Notification System for MetaBrainz

Centralized Notification System for MetaBrainz

Personal Information:

Name: Shaik Junaid
Email: [redacted]
IRC Nickname: fettuccinae
Github: fettuccinae
Timezone: UTC +05:30

Introduction:

Hi! I’m Junaid, a second-year Computer Science student at Mahatma Gandhi Institute of Technology (MGIT) ,Hyderabad, India.

I love listening to music and I am fascinated by data related to music habits. When I discovered MetaBrainz through the GSoC organizers list, I knew it was the perfect match.

Project summary:

Title: Centralized Notification System for Metabrainz and its projects
Proposed Mentor: Lucifer
Project Length: 350 hours
Short Description: This project will add a notification system so users can be kept up to date with events, reactions, new playlists and features, etc.
Expected Outcome: A functional notifications system with relevant API endpoints.

Implementation:

We need to create a notification system in the MetaBrainz that hosts multiple endpoints which then are used by MeB projects to send notifications out to users.

1. Create a table to store Notifications

Schema

id:			    serial no.
user_id: 		INT, musicbrainz id
user_email:     TEXT, unique email
project: 		postgres enum, of which project sent the notification.
read:			BOOLEAN
createdt:		TIMESTAMP
expire_age:		SMALLINT (days)
important:		BOOLEAN. If important skip digest and always send mail. 
email_id:		TEXT, unique email ID
subject:		TEXT, mail subject
body:			TEXT, mail body
digest:         BOOLEAN
digest_time:    SMALLINT (days). If digest, digest_time should be given else NULL.

2. Endpoint for sending notifications

I will create a post endpoint /notification/send which is protected and takes in the notification sent by projects and write it into the notifications table. if its marked as important or not digest , it will send mail using brainzutils send_mail function.

In views.py

@oauth2_bp.route("/notification/send", methods=["POST"])
@require_oauth("notifications.write")

def send_notifications():

      store_notification(dict)

      if important or not digest:
           send_mail()
           mark_read()
 

message body example :

[{
	"user_id": "3",
	"project": "listenbrainz",
	"to": "user@example.com",
	"reply_to": "noreply@listenbrainz.org",
	"sent_from": "noreply@listenbrainz.org",
	"Subject": "YIM 2025", 
	"body": "Year In Music for your account is out",
	"important": false.
	"expire_age": 30
	"email_id": " ",
	"send_email": True,
    "digest": True,
    "digest_age": 7
}, … ]

3. Endpoint to fetch notifications

I will create a GET endpoint /notification/fetch to fetch notifications.
Parameters:
project : Required, String representing the lowercase name of the project
until_ts : Optional, if not provided use now(). Notifications up until this timestamp will be returned.
offset : Optional, 0 if not provided.
count : Optional, 25 if not provided.
unread-only : Optional, must be either “t” or “f”. Defaults to False.

This will return matched data from Notifications table.
return example:

[{
	"id": 1,
    "user_id": "3",
	"project": "listenbrainz",
	"body": "Year In Music for your account is out.",
	"sent_at": <timestamp>,
    "read": false,
	"important": false,
       
}, … ]

4. Endpoint to Mark notifications as read/unread

I will create a POST endpoint /notification/mark for user to mark a notification as read or unread.
In views.py

@oauth2_bp.route("/notification/mark", methods=["POST"])
@require_oauth("notifications.write")
def mark_notif():
    for i in read:
           mark_read(i)
    for j in unread:
           mark_unread(j)

User needs to make the request with access token of a notification.write scope and body should contain list of notification id’s of messages that should be marked read and unread.
example API request

POST /notification/mark HTTP/1.1
Host: metabrainz.org
Authorization: Bearer access_token
Content-Type: application/json

{
    "read": [1, 2, 3],
    "unread": [4, 5]
}

5. Endpoint to Delete notifications

I will create a POST endpoint /notification/delete for a user to delete their notifications.
In views.py

@oauth2_bp.route("/notification/delete", methods=["POST"])
@require_oauth("notifications.write")
def mark_notif():
    for i in list:
         delete_notification(i)
           

User needs to make the request with access token of notification.write scope and the body should contain list of notification id’s that should be deleted.
Example API request

POST /notification/delete HTTP/1.1
Host: metabrainz.org
Authorization: Bearer access_token
Content-Type: application/json

{
   [5,6,7,8]
}

6. Cron Job to send/delete notifications

Run a cron job everyday to check if digest age is reached and if true, send mail to the user.

digest.py

def digest():
     for i in table:
         if not i.read:
               if i.digest_age ==  now() - i.createdt:
                   send_mail()

Run a cron job everyday to check if the notification has expired, if true, delete the notification

delete_notifications.py

def delete_notif():
       for i in table:
             if i.expiry_age <= now() - i.createdt:
                   delete_notification(i)

7. Integration into ListenBrainz

Generate client ID and client secret from metabrainz and insert into client credentials table.
Add that clientID and client secret into the config.py .

Add digest and digest_days column into user’s table.
Add an option in settings which allows users to set how they’d like to recieve mails and set the digest_days.
Add backend logic to save these preferences.

Create notification_sender.py and initialize it with env client ID and client secret.

notification_sender.py

class NotificationSender:
        def __init__(self, base_url, client_id, client_secret):
            self.base_url = base_url
            self.client_id = client_id
            self.client_secret = client_secret
            self.access_token = None
            self.expires_at = 0
        def _generate_token(self):

        def _validate_token(self):

        def send_notifications(self, user_id, project, body, subject=None, 
                           send_email=False, email_digest=False, digest_days=7, 
                           expire_age=30, important=False, email_id=None):


Replace send_mail with send_notification in methods which send out mails to users (YIM, account paused, account unpaused, data export, Import errors, etc).
Give appropiate parameters in send_notifications, example: Set the important flag as true if the user is paused.

Create mail templates for events which change user’s timeline and start sending out notifications for those events (track being recommended by a user, weekly playlist generated, etc).

Create a script which sends out notifications about new features and run it everytime a feature is added.
new_feature.py

def notify_users():
      subject = "new feature"
      body = "feature.txt"
      for user in users:
          send_notification(user.id, listenbrainz, body, subject, 
                           send_email=True, email_digest=user.digest, digest_days=user.digest_days, 
                           expire_age=30, important=False, email_id=None)

8. Integration into BookBrainz

I will add two columns in bookbrainz editor table which are digest(boolean) and digest_days(INT) and add an option inside user profile to let users choose if they want to recieve mails individually or in a digest and if they select digest, then let them decide digest_days.

I will create a new npm module called brainzutils-js which will contain NotificationSender.js for now, but later can be used to add more functions.

NotificationSender.js

class NotificationSender {
    constructor(baseUrl, clientId, clientSecret) {
        this.baseUrl = baseUrl;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.accessToken = null;
        this.tokenExpiresAt = 0;
    }

    async _GenerateToken()
 
    async _ValidateToken()

    async sendNotification({
        userId, project, body, subject = null, sendEmail = false, emailDigest = false,
        digestDays = 7, expireAge = 30, important = false, emailId = null
    }) 
export default NotificationSender;

Generate a client ID and client secret in metabrainz, insert it into client credentials table of metabrainz and then save them into config.json.

Timeline:

Community bonding period
May 8 - May 11 :
setting up local development environment, Orientatino

Phase 1
(May 12 - June 8) :

Week (1-4):

  • Create notification table.

  • Create an endpoint to send notifications.

  • Create an endpoint to fetch notifications

  • Write tests and documentation for these endpoints.

  • Create an endpoint to mark notifications as read/ unread

  • Create an endpoint to delete notifications

  • Write tests and documentations for these endpoints

  • Create cron job for sending notifications for digest

  • Create cron job for clearing notifications past their expiry age

June 9- July 13:
Week (5-10):

Integration into listenbrainz

  • Set up local development environment

  • Import NotificationSender from brainzutils and initalize it with oauth credentials inside a file notifSender.py

  • Replace send_mail function in listenbrainz to send_notification from notifSender.py

  • Notification options are to be set using parameters in send_notification function.

  • Add option in user settings to set user’s digest preferences.

  • Create email templates for events that alter the user’s timeline.

  • send_notification for all these events and set their priorities accordingly.

  • Create a template for new feature notification.

  • Create a script which is to be run when new features are added.

  • Write tests and docs

Phase 2
(July 14 - August 3):

Week (11-14):

Integrate into bookbrainz:

  • Set up local development environment

  • Create a new js module with a function NotificationSender which contains methods to get oauth credentials and to send notifications

  • Write tests and docs for this module.

  • Import this module and set oauth credentials.

  • Change user schema to include if user chose digest instead of individual mail and no of days between digest.

  • Add option in user profile to set the settings for user’s digest preferences.

  • Create a template for new feature addition

  • Create a script which is to be run when a new feature is added.

  • Write tests and docs

August 4 - August 24 :
Week(15 - 17):

Main project is completed

  • If something took longer than expected, This is the buffer for that.

  • Prepare final Gsoc report.

Additional Integrations:

Extended Timeline

August 25 - November 9

After my main project, I plan to work to integrate more MeB projects into the notification system.

  • Integrate MB-mail service into notification sender.

  • Integrate MusicBrainz

  • Integrate MusicBrainz subprojects (Picard, CAA, EAA)

Why me:

I have been contributing to ListenBrainz since January 2024, gaining a solid understanding of its codebase. Here are my pull requests

One of my contributions involved implementing a feature that allows admins to pause users. Through this, I explored how notifications are sent within MetaBrainz and recognized the need for a centralized notification system. With this experience, I am well-prepared to work on this project.

Detailed information about yourself

What type of music do you listen to?

I listen to a wide variety of music, from shoegaze rock and indie to pop, hiphop and trap.

Some of my recent favorite tracks:
Welcome to the Machine” from the album Wish you were here by Pink Floyd
Fade Into You” from the album So Tonight That I Might See by Mazzy Star
Art Deco” from the album Honeymoon by Lana Del Rey
Kissland” from the album Kissland by The Weeknd
Married to the Game” by Future and Esco
Count me Out” from the album Mr. Morale & the Big Steppers by Kendrick Lamar
Survival” from the album Scorpion by Drake

What aspects of the project you’re applying for interest you the most?

I love the stat collection of listenbrainz and the algorithms used to generate playlists based on my listening history and taste.

Im excited for keeping users more in the loop by sending them notifications when playlists are generated and when other events occur.

When did you first start programming?
I started programming in the first year of my engineering course.

Tell us about the computer(s) you have available for working on your SoC project!
I have a MSI GL63 Laptop with 16GB RAM and 1TB SSD running Windows 11

Have you contributed to other Open Source projects? If so, which projects and can we see some of your code?
Listenbrainz is the first Open Source project that I have contributed to.
These are my Closed PRS in LB.

What sorts of programming projects have you done on your own time?
My friends and I created a full stack website for digitalizing scholarship grants using html,css, django and sqlite.
A webserver in C.
Voice memo to youtube uploader.

How much time do you have available, and how would you plan to use it?

I can contribute 40 hours/ week , I have my final exams from 1st july to 19th july, so i can contribute 15 hours/ week during this time. I will make up for my exams by starting the coding period early and contribute around 40 hours/week in may-june and then in Late july-august as we get a break after exams.

2 Likes

Hi!

Thanks for the proposal.

I am not sure if we want to do Client Credentials flow or just the regular authorization code flow (using an admin user) but either way I am working on OAuth stuff these days and can implement this before the project begins so you can focus on just the notifications itself.

You can skip the BU integration and just integrate directly in LB. Also the LB integration should be done before BB integration. You can also skip integrating in CB.

While the digest settings UI should be available in each project for now, the settings itself should live in a common table in MeB itself.

2 Likes