This project implements a notification system having the following requirements:
- The system needs to be able to send notifications via several different channels (email, sms, slack) and be easily extensible to support more channels in the future.
- The system needs to be horizontally scalable.
- The system must guarantee an "at least once" SLA for sending the message.
- The interface for accepting notifications to be sent is an HTTP API.
- API server handles the notification requests, validates them and sends the corresponding messages to the RabbitMQ
- Worker reads messages from the queues and processes them according the requested message provider
- Status Database - both API Server and Worker save notification's current status and the number of attempts tried to send the message
- Rabbit MQ receives the messages from the API Server and sends unacknowledged messages to the corresponding DLQ (dead letter queue)
The system supports two endpoints
POST /notifications
for sending a notification
// Send sms
curl --location '<api_url>/notifications' \
--header 'Content-Type: application/json' \
--data '{
"channel": "sms",
"recipient": "+359888888888",
"message": "Hello from the notification system!",
}'
// Send a slack message
curl --location '<api-url>/notifications' \
--header 'Content-Type: application/json' \
--data '{
"channel": "slack",
"recipient": "C08NKAKQ4N3", // channel ID
"message": "Hello from the notification system!",
}'
// Send an email
curl --location '<api-url>/notifications' \
--header 'Content-Type: application/json' \
--data-raw '{
"channel": "email",
"recipient": "email@example.com",
"message": "Hello from the notification system!",
"metadata": {"email_subject": "My subject"}
}'
GET /notification/:id/status
for getting notification status
curl --location '<api-url>/notifications/<notification-id>/status'
This is a Postman collection to which you can also refer.
- Go 1.24.1
- Docker
- Ginkgo (
go install github.com/onsi/ginkgo/v2/ginkgo
)
Create a .env
file in the root directory based on .env.example
and make sure to fill in the proper values there.
To configure RabbitMQ you need a running instance of RabbitMQ to fill in the needed configuration values.
The system uses Twilio for sending sms messages. Follow the steps provided by Twilio to create an account and an active number. For test accounts you need to add the numbers that will receive the notifications as verified caller IDs.
For Slack notifications, the system uses a Slack app with registered Slack bot token. The app should be added to a Slack workspace and invited to the channel where the notifications will be sent.
The system uses Twilio SendGrid to send emails. You need to provide Twilio Sendgrid API key in .env for the notifications to work properly.
The database is used to save the current status of each message. It can be checked anytime using the GET /notifications/:id/status
endpoint.
You can use DBeaver or similar tool to review database.
-
Set up your environment:
cp .env.example .env # Edit .env with your configuration as described in the previous section
-
Destroy tests containers (if any)
docker-compose --env-file .env.test -f docker-compose.yml -f docker-compose.test.yml down
-
Start the required services:
docker-compose up -d
-
Run the API service:
go run cmd/api/main.go
-
Run the worker service (in a separate terminal):
go run cmd/worker/main.go
The API will be available at http://localhost:8080
RabbitMQ Web UI will be available at http://localhost:15672/
ginkgo -v pkg/validation
Integration tests use real instances of RabbitMQ and PostgreSQL and mocked notification providers for sms, slack and email.
To run the integration tests, execute the following commands :
docker compose down
cp .env.test.example .env.test
docker-compose --env-file .env.test -f docker-compose.yml -f docker-compose.test.yml up -d
DB_HOST=localhost RABBITMQ_HOST=localhost go test -v ./tests
For production deployment:
-
Set up a production-grade PostgreSQL database
-
Configure a production RabbitMQ instance
-
Configure SSL/TLS for secure communication
-
Set up proper environment variables in your deployment environment
-
Build and deploy the API and worker services:
# Build the services go build -o api cmd/api/main.go go build -o worker cmd/worker/main.go # Deploy the binaries to your production environment
-
Consider using a process manager like
systemd
orsupervisor
to keep the services running -
Consider using a load balancer like
nginx
ro route requests to multiple instances of the API server -
Consider running multiple workers for better scalability
-
Set up proper monitoring and logging
-
Automate infrastructure and deployment using Ansible, Terraform or any other Infrastructure as Code (IaC) tooling
- Protect the API with API keys
- Protect the API with rate limiting - both general and per user
- Write more tests to ensure quality
- Support rich formatting of the messages
- Add Slack channel validation for existence and permissions to send messages to that channel
- Document the API with Swagger or similar tool