A system to send notifications to users.
This submission underlines the following (the striked out ones are not covered):
Notification Service:
Objective:
Build a system to send notifications to users.
Requirements
- API Endpoints:
- Send a Notification (POST /notifications)
- Get User Notifications (GET /users/{id}/notifications)
- Notification Types:
- Email, SMS, and in-app notifications.
- Bonus Points:
- Use a queue (e.g., RabbitMQ, Kafka) for processing notifications.
- Add retries for failed notifications. (unstable)
Deliverables
- A Git repository link containing:
- Source code.
- README with setup instructions and any assumptions made.
(Optional) Link to the deployed applicationor API documentation (if implemented).
-
Make sure you have Docker installed and running on your system.
-
Clone this repository and
cd
into it:git clone https://github.com/BillyDoesDev/notificationd.git cd notificationd
-
If you haven't already, create an account on MailGun. This will be used to create and send emails.
- Follow their welcome guide to set up your account, and obtain your API keys, and configure your test email. Save these for later.
-
Now, create an account on Twilio. This handles SMS notifications.
- Obtain a virtual phone number and your auth token from their dashboard, and save these for later.
-
Create a
.env
file in the current directory. Its contents should have the following. Replace the fields as required.
Important
Keep the MONGO_URI
as is.
MAILGUN_API_KEY="xxx"
MAILGUN_DOMAIN="xxx.mailgun.org"
REGISTERED_RECEIVER_EMAIL="John Doe <[email protected]>"
TWILIO_SID="xxx"
TWILIO_AUTH_TOKEN="xxx"
TWILIO_PHONE_NUMBER="+1xxx"
RECEIVER_PHONE_NUMBER="xxx"
RABBITMQ_QUEUE="hello"
RABBITMQ_EXCHANGE="notification-exchange"
RABBITMQ_ROUTING_KEY="hello"
The directory structure should look something like this:
.
├── compose.yaml
├── Dockerfile
├── .dockerignore
├── .env
├── .gitignore
├── LICENSE
├── main.py
├── README.md
├── requirements.txt
├── scripts
│ └── notification_worker.py
├── static
│ ├── css
│ └── main.js
└── templates
└── index.html
-
Create a python virtual environment, activate it and install the dependencies.
python -m venv env source env/bin/activate # env\scripts\activate # for Windows users pip install -r requirements.txt
-
Inside the project directory, run:
docker compose up --build -d
-
This will start the docker container, which manages essential dependencies for the project. Once that's up, start up your python servers, one for the primary API endpoint, and the other for managing the
RabbitMQ
broker.python main.py
And in a separate terminal:
python scripts/receive_v2.py
-
The API endpoints are live at
http://localhost:5050
. You can head over there in your browser to try out a web client for the API. -
To access a GUI to the database, you can head over to
http://localhost:8081
to access themongo-express
dashboard for the MongoDB database. -
To shut the server down, simply run the following, and kill the python processes:
docker compose down
Tip
If you do not make any changes to the files, to run this next time, you can simply do:
docker compose up -d
This does not rebuild the containers each time.
The following endpoints are available:
-
Post a new notification
Sends a request for a new notification to be sent.
-
URL
/notifications
-
Method:
POST
-
URL Params
Required:
None
-
Data Params
{ "user_id": 4, "notification_type": "in-app", "content": "hello, world!" }
The
user_id
key can be any integer, denoting some user id in a real system.
Thenotification_type
key can be eitherin-app
|sms
|email
.
Thecontent
key is the message you want to send in your notification. -
Success Response:
-
Code: 201
Content:{"message": "Notification queued", "id": "6829ce9ed71d22a7cfbf86db"}
The
id
key refers to the uniqueObjectID
of the notification as stored in the MongoDB. Queries on this notification may be performed using this.
-
-
Error Response:
- Code: 500
- Code: 500
-
Sample Call:
curl -X POST 'http://localhost:5050/notifications' \ -H 'Content-Type: application/json' \ --data-raw '{"user_id":4,"notification_type":"in-app","content":"hello, world!"}'
-
-
Get Notifications
Returns notifications for a certain user id.
-
URL
/users/:id/notifications
-
Method:
GET
-
URL Params
Required:
id=[integer]
-
Data Params
None
-
Success Response:
-
Code: 200
Content:{"data": [ { "user_id": 4, "notification_type": "email", "content": "message content", "status": "sent", "timestamp": { "$date": "2025-05-18T09:20:09.362Z" } }, ... ]}
The
status
key can have valuespending
|sent
|failed
. In case a notification hasn't been sent yet, it will be retried after every 10s.
Thetimestamp
key shows the UNIX timestamp of the last notification status.
-
-
Error Response:
- Code: 400
Content:{"error": "No notifications for user_id found."}
- Code: 400
-
Sample Call:
curl 'http://localhost:5050/users/4/notifications'
-
- This app runs in a
python-flask
server, and usesMongoDB
to store user notifications. The entire setup is dockerised, to provide a seamless deployment experience, and also to allow room for scalability. - In-app updates are managed via
websockets
, usingsocket.io
, on both the client and the server. - SMS and Email updates are handled via
RabbitMQ
, which serves as a message broker, to deliver notifications gracefully, and handle large workloads.- Essentially, when a new
POST
request is sent, RabbitMQ comes into action, via thescripts/receive_v2.py
script, which manages a suitable exchange to deliver messages, along with separate queues, to deal with successful and failed attempts. As of wriring this, theretry
feature is a bit unreliable.
- Essentially, when a new
MailGun
andTwilio
are used to send email and SMS updates, respectively.
Tip
[Note that this is completely optional] If you prefer having socket.io.js
fully offline, you can get it using:
curl -O https://raw.githubusercontent.com/BillyDoesDev/blueberry/refs/heads/main/> static/socket.io.js
And then simply link that to your index.html
Note
Previously, before RabbitMQ
was implemented in this project, messages were polled at fixed intervals using apscheduler
, by looking at database records. You can browse that version of the project here.
This project is open source, under the MIT License.
MIT License
Copyright (c) 2025 BillyDoesDev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.