Skip to content

0xDVC/notification-system

Repository files navigation

notification service

this project is our notification system. it handles sending emails, sms messages, and push notifications. the goal was to build something reliable and flexible so we can easily swap out external services if needed.

overview

from the start, i wanted something robust and flexible. that's why i went for a hexagonal architecture approach. imagine it like this:

the core brain: this is where our actual notification logic lives. it knows what a notification is, how to decide if it should be sent, and what it should look like. it doesn't care how it gets sent or where the data comes from. it just focuses on the business of notifying.

the action plans: this layer takes requests (like "send a welcome email") and orchestrates the steps. it talks to the brain for decisions and then asks the outside world(specific connections in this case) to do the actual sending or saving.

the outside connections: these are the parts that actually plug into real-world services like databases, email senders, sms provider, or push notification services. the cool thing is, our brain and action plans don't know or care about the specifics of these, they just use the 'connections'. this means we can swap out one service for another service without touching our core logic.

the idea was to practice the concept of DDD, whilst introducing a bit of complexity whilst applying it. for this use case, the separation keeps everything tidy and makes it much easier to test and adapt.

architecture

Notification Service Architecture

how it works?

when a request comes in (like "send this notification"), it first hits caddy, which is our reverse proxy. caddy takes care of things like https and routing, and then passes the request along to our notification service. inside the notification service, the request goes through a few layers:

  • rest api layer: this is where we handle incoming http requests. it takes the data, does some basic validation, and hands it off to the business logic.
  • business layer: here’s where the real work happens. this layer figures out what needs to be done—should we send an email, an sms, a push notification, or maybe all three? it also applies any business rules, like checking user preferences or rate limits.
  • domain layer: this is the heart of the system. it knows what a notification is, what it means to send one, and how to track its status. it’s totally unaware of how things get sent out or stored.
  • persistence layer: when we need to save or fetch data (like notification history or user preferences), this layer talks to the database. it keeps the rest of the system blissfully ignorant of sql or database details.

once the business logic decides what to do, it uses the output ports to actually send the notification. these are like plug sockets—the business logic just plugs in and doesn’t care what’s on the other end. the actual sending is handled by adapters that talk to real-world services:

  • ses for email
  • mnotify for sms
  • firebase cloud messaging for push notifications

if we ever want to swap out one of these services, we just change the adapter

the database keeps track of everything that’s sent, so we can check statuses, retry failures, and keep a history. all of this runs inside docker containers, so it’s easy to spin up, test, and deploy anywhere. in short: requests come in, get routed, processed, and sent out through whatever channel is needed, all while keeping the core logic clean and easy to maintain.

requirements

  • java 17
  • maven
  • docker
  • git

tools and services used

  • spring boot
  • thymleaf
  • tailwindcss
  • postgresql(prod) and h2(dev)
  • caddy(prod)
  • aws ses
  • mnotify sms
  • firebase cloud messaging

config

# database
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/notification_service
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=password

# aws ses (email)
AWS_SES_ACCESS_KEY=your-aws-access-key
AWS_SES_SECRET_KEY=your-aws-secret-key
AWS_SES_REGION=us-east-1
[email protected]

# mnotify (sms)
MNOTIFY_API_KEY=your-mnotify-api-key
MNOTIFY_API_URL=https://api.mnotify.com/api
MNOTIFY_SENDER=YourApp

# firebase (push)
FIREBASE_PROJECT_ID=your-firebase-project-id
FIREBASE_CREDENTIALS_PATH=/app/firebase-credentials.json

# app
SPRING_PROFILES_ACTIVE=prod

prroduction config: email, sms, and push credentials

To run this service in production, you need to provide credentials for AWS SES (Email), MNotify (SMS), and Firebase Cloud Messaging (Push). Here’s what you need to set up and where to put it.

AWS SES (Email)

  • Docs: https://aws.amazon.com/ses/
  • You need:
    • AWS_SES_ACCESS_KEY
    • AWS_SES_SECRET_KEY
    • AWS_SES_REGION (e.g. us-east-1)
    • AWS_SES_FROM_ADDRESS (must be a verified sender)
  • Put these in your production.env or as environment variables in Docker Compose:
    AWS_SES_ACCESS_KEY=your-access-key
    AWS_SES_SECRET_KEY=your-secret-key
    AWS_SES_REGION=us-east-1
    AWS_SES_FROM_ADDRESS=[email protected]

MNotify SMS

  • Docs: https://readthedocs.mnotify.com/#tag/SMS
  • You need:
    • MNOTIFY_API_KEY (from your MNotify dashboard)
    • MNOTIFY_API_URL (usually https://api.mnotify.com/api)
    • MNOTIFY_SENDER (your registered sender name)
  • Put these in your production.env or as environment variables:
    MNOTIFY_API_KEY=your-mnotify-key
    MNOTIFY_API_URL=https://api.mnotify.com/api
    MNOTIFY_SENDER=YourSenderName

Firebase Cloud Messaging (FCM)

  • Docs: https://firebase.google.com/docs/admin/setup
  • You need:
    • A Firebase service account JSON file
  • How to get it:
    1. Go to the Firebase Console
    2. Project Settings > Service Accounts
    3. Generate new private key and download the JSON
  • Place the file in your project root as firebase-service-account.json (or whatever you want to call it)
  • In Docker Compose, mount it and set the env var:
    volumes:
      - ./firebase-service-account.json:/app/classes/notification-system.json:ro
    environment:
      - GOOGLE_APPLICATION_CREDENTIALS=/app/classes/notification-system.json

How to run everything

  1. Make sure your credential fields above are set in production.env and your Firebase JSON is in place.
  2. Start the stack:
    docker-compose down -v
    docker-compose up --build
  3. The API will be at http://localhost:8080/api
  4. Swagger UI: http://localhost:8080/api/swagger-ui.html or /api/swagger-ui/index.html
  5. Thymeleaf UI: http://localhost:8080/api/templates

If you miss any credential or set a wrong value, the service will fail to send through that channel and you’ll see a clear error in the logs or API response.

About

notification microservice with Hexagonal Architecture

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published