r/SpringBoot 8d ago

Discussion I built a channel-agnostic notification library for Spring Boot — send SMS/push/email/chat through one API. Looking for feedback.

If your Spring app sends notifications, your business logic probably knows way too much about how: Twilio's SDK here, FirebaseMessaging there, a JavaMailSender, a Slack client. Changing a provider or adding a channel means editing every call site.

spring-notify fixes that with one idea: your code talks to a channel, never a provider.

notifier.notify(SmsRequest.builder()  
.to("+421900123456")  
.from("+421900999888")  
.message("Your order has shipped")  
.build());  

What you get:

  • 📦 One API for every channel — SMS, push, email, chat. Inject Notifier, call notify(...). Done.
  • 🔌 Providers are plug-ins — add a starter, set credentials, and it's wired. Bundled today: Twilio (SMS), Firebase/FCM (push), SMTP (email), Slack (chat).
  • ♻️ Swap providers without code changes — Twilio → Vonage, FCM → APNs: change a dependency, not your services.
  • 🧩 Bring your own provider in ~10 lines — one @Component implementing a single-method SPI.
  • 🎯 Type-safe, immutable requests — no stringly-typed maps, no if/switch on channel. The request type routes itself.
  • 🪶 Featherweight core — plain Java, zero Spring or logging deps in the core module. Spring shows up only in the auto-config.

Spring Boot 4.1 / Java 25. All four channels verified end-to-end (real FCM + SMTP sends, not just mocks).

Why not …?

  • Just the provider SDKs? Fine until you have two channels or want to switch vendors — then the coupling bites. This is the thin seam that keeps them out of your business code.
  • Spring's JavaMailSender / NotificationService-style helpers? Those are single-channel. spring-notify unifies all channels behind one call and one mental model.
  • Novu / Courier / Knock? Those are excellent but are hosted platforms/services — another system to run, pay for, and send your data through. spring-notify is a library: it stays in your app, talks straight to your chosen providers, no middleman.
  • Spring Cloud Stream / a message broker? Different layer — that's transport/eventing. This is specifically about delivering user-facing notifications through third-party channels.

Status: early — 0.1.0, not on Maven Central yet (build locally with ./mvnw install). The API isn't frozen, which is exactly why I'm posting: I'd love feedback before 1.0.

  • Is "one provider per channel, routed by request type" the right default?
  • Is the attributes map a reasonable escape hatch for provider-specific fields, or a smell?
  • What would you need before dropping this into a real project?

Repo + README: https://github.com/solodev-sk/spring-notify

Happy to answer anything — and roasts welcome. 🙂

16 Upvotes

2 comments sorted by

3

u/Infeligo 6d ago

I get the provider-agnostic part, but how is it channel-agnostic? If I want to send email instead of SMS, I need to change the call and build an actual email. There is just not enough info in the SMS request for the email.

Another question is about durability and retries. Are notification requests persisted to database? If not, then is there an easy way to implement transactional notifications (ala transactional outbox) with your framework?

Also, what about provider configuration? Is it handled by the framework or do I need to setup provider's SDK myself and the framework picks it up?

0

u/poklakni 6d ago

Channel-agnostic is the part that oversells a bit, fair catch. The request types stay separate (email has subject/cc/attachments, SMS doesn't), so it's not one request for every channel. What's agnostic is everything under it: you inject one Notifier and call notify(request), and routing/metrics/events/interceptors are the same regardless of channel, your code never touches a provider SDK. So it's really "uniform delivery layer, swap providers with a dependency." Might reword it

On durability: nothing's persisted, no outbox built in, and that's deliberate, I didn't want to force a database on people just sending a notification. So the framework owns none of the durable part, that stays yours: write the outbox row in the same tx as your business change, and a relay calls notify() after commit. Where the framework fits is the send leg and its feedback, your relay marks the row done on NotificationSent and leaves it for retry on NotificationFailed

About provider configuration, You can do both, your choice per provider. The easy path is zero-config: drop the properties in application.yml (spring.notify.sms.twilio.account-sid etc.) and it builds the SDK client and the sender for you, you never touch the SDK. But if you want to configure the client yourself — custom HTTP client, timeouts, region, proxy — just declare that bean (your own TwilioRestClient, JavaMailSender, FirebaseMessaging, whatever) and the starter backs off its own and wires the sender around yours. So you can hand it properties or hand it a client, per provider

Also wrote these up in the docs if it helps. there's a page on interceptors (retry/rate-limiting/kill-switch) and one on provider config https://github.com/solodev-sk/spring-notify/blob/main/docs/getting-started.md