Reactive, provider-agnostic notifications core for Spring Boot — email, SMS and push ports with pluggable SendGrid, Resend, Twilio and Firebase adapters, FreeMarker templating, per-user channel preferences and built-in Micrometer metrics.
- Overview
- Features
- Requirements
- Installation
- Quick Start
- Configuration
- Documentation
- Contributing
- License
Firefly Framework Notifications is the core notifications abstraction for the Firefly platform. It defines the application services and outbound ports for sending notifications across three channels — email, SMS and push — and lets you swap concrete delivery providers without touching business logic.
The module follows a strict hexagonal (ports & adapters) design. Your code depends only on the channel services (EmailService, SMSService, PushService) and the outbound port interfaces (EmailProvider, SMSProvider, PushProvider). The actual integration with a SaaS provider lives in a separate adapter module (SendGrid, Resend, Twilio, Firebase) that you add as a dependency and select with a single property. Because the ports are the only contract, you can switch from, say, SendGrid to Resend by changing a dependency and one config line — no code changes.
Everything is reactive end to end: services and ports return Project Reactor Mono types, so notification dispatch composes naturally into WebFlux pipelines and never blocks request threads. On top of the raw send path the core adds two cross-cutting concerns out of the box: a FreeMarker template engine for rendering templated emails by ID, and a notification preference service so deliveries can respect per-user, per-channel opt-outs. A Spring Boot auto-configuration wires shared Micrometer instrumentation (NotificationMetrics) that every adapter reuses to emit consistent send/latency/error metrics tagged by channel and provider.
This repository ships only the contracts, DTOs, services and observability — it has no opinionated runtime provider of its own. Pick one (or more) of the sibling adapter modules below to actually deliver messages.
This is an aggregator (pom packaging) project. It currently contains a single submodule; the structure leaves room for additional core submodules without breaking coordinates.
| Module | Artifact | Purpose |
|---|---|---|
| Notifications Core | fireflyframework-notifications-core |
Channel services (email/SMS/push), outbound provider ports, request/response DTOs, FreeMarker template engine, in-memory notification preference service, and Micrometer observability auto-configuration. |
Concrete delivery is supplied by these separate adapter repositories, each implementing the matching port from this core and activating via a firefly.notifications.<channel>.provider property:
| Adapter | Channel | Implements | Selecting property |
|---|---|---|---|
fireflyframework-notifications-sendgrid |
EmailProvider |
firefly.notifications.email.provider=sendgrid |
|
fireflyframework-notifications-resend |
EmailProvider |
firefly.notifications.email.provider=resend |
|
fireflyframework-notifications-twilio |
SMS | SMSProvider |
firefly.notifications.sms.provider=twilio |
fireflyframework-notifications-firebase |
Push | PushProvider |
firefly.notifications.push.provider=firebase |
- Reactive channel services —
EmailService,SMSServiceandPushService, each returningMono<...ResponseDTO>for non-blocking dispatch. - Hexagonal outbound ports —
EmailProvider,SMSProvider,PushProvider. Add an adapter module and a config property to bind an implementation; no code changes to switch providers. - Rich email model —
EmailRequestDTOwithfrom/to, CC/BCC lists, plain-text and HTML bodies, Bean Validation (@Email,@NotBlank), andEmailAttachmentDTOfor byte-content attachments with MIME type. - Templated email —
sendTemplateEmail(EmailTemplateRequestDTO)renders a template bytemplateIdwith a variable map before delivery. Backed by the pluggableNotificationTemplateEngineSPI. - FreeMarker template engine —
FreemarkerNotificationTemplateEngineloads.ftltemplates from a configurable classpath prefix (default/notification-templates) and an optional filesystem directory; rendering runs on a bounded-elastic scheduler. - Per-user channel preferences —
NotificationPreferenceServicewith a ready-to-useInMemoryNotificationPreferenceService; toggle email/SMS/push per user (with per-channel overrides) and checkisChannelEnabled(userId, channel)before sending. - Consistent response contracts —
EmailResponseDTO(withEmailStatusEnumSENT/FAILED andsuccess(...)/error(...)factories),SMSResponseDTO, andPushNotificationResponse, each carrying message ID, status and error message. - Built-in observability —
NotificationObservabilityAutoConfigurationregisters a sharedNotificationMetricsbean (when aMeterRegistryis present) emittingfirefly.notifications.sent,firefly.notifications.delivery.duration,firefly.notifications.errorsandfirefly.notifications.templates.rendered, tagged bychannel,provider,statusandtemplate. - Reusable by every adapter — adapters wrap their dispatch in
NotificationMetrics.timedDispatch(channel, provider, mono)so all providers report metrics the same way.
- Java 21+ (Java 25 recommended)
- Spring Boot 3.x
- Maven 3.9+
- At least one provider adapter (SendGrid, Resend, Twilio, or Firebase) plus its account/API credentials to actually deliver notifications
- A
MeterRegistry(e.g. Micrometer + Actuator) on the classpath if you want notification metrics - FreeMarker on the classpath only if you use templated email (it is an optional dependency)
The notifications core is consumed transitively by every provider adapter, so in most applications you simply add the adapter you need (see Provider Adapters). To depend on the core contracts directly:
<dependency>
<groupId>org.fireflyframework</groupId>
<artifactId>fireflyframework-notifications-core</artifactId>
<!-- Version is managed by the Firefly BOM / parent; omit it when inheriting fireflyframework-parent -->
</dependency>The version is governed by the Firefly Framework parent/BOM. When your project inherits fireflyframework-parent (or imports the Firefly BOM), you can omit the <version> entirely.
1. Add a provider adapter (here: SendGrid for email). The adapter brings fireflyframework-notifications-core in transitively.
<dependency>
<groupId>org.fireflyframework</groupId>
<artifactId>fireflyframework-notifications-sendgrid</artifactId>
</dependency>2. Select and configure the provider in application.yml:
firefly:
notifications:
email:
provider: sendgrid # binds the SendGrid EmailProvider adapter
sendgrid:
api-key: ${SENDGRID_API_KEY}3. Inject the channel service and send — the EmailProvider adapter is auto-wired behind EmailService:
import org.fireflyframework.notifications.core.services.email.v1.EmailService;
import org.fireflyframework.notifications.interfaces.dtos.email.v1.EmailRequestDTO;
import org.fireflyframework.notifications.interfaces.dtos.email.v1.EmailResponseDTO;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class OrderNotificationService {
private final EmailService emailService;
public OrderNotificationService(EmailService emailService) {
this.emailService = emailService;
}
public Mono<EmailResponseDTO> sendOrderConfirmation(String customerEmail, String orderId) {
EmailRequestDTO request = EmailRequestDTO.builder()
.from("orders@example.com")
.to(customerEmail)
.subject("Order Confirmation - " + orderId)
.html("<h1>Thanks!</h1><p>Your order " + orderId + " is confirmed.</p>")
.build();
return emailService.sendEmail(request);
}
}Templated email — provide a NotificationTemplateEngine bean (the FreeMarker engine renders .ftl files from /notification-templates) and call sendTemplateEmail:
@Bean
NotificationTemplateEngine notificationTemplateEngine() {
return new FreemarkerNotificationTemplateEngine(); // classpath:/notification-templates
}
// ...
EmailTemplateRequestDTO request = EmailTemplateRequestDTO.builder()
.from("welcome@example.com")
.to(user.getEmail())
.subject("Welcome!")
.templateId("welcome-email") // resolves welcome-email.ftl
.templateVariables(Map.of("firstName", user.getFirstName()))
.build();
return emailService.sendTemplateEmail(request);Respect user preferences before dispatching:
return preferenceService.isChannelEnabled(userId, "email")
.filter(Boolean::booleanValue)
.flatMap(enabled -> emailService.sendEmail(request));SMS and push follow the same pattern via SMSService.sendSMS(SMSRequestDTO) and PushService.sendPush(PushNotificationRequest), selecting the Twilio and Firebase adapters respectively.
This core module exposes one auto-configuration toggle; the bulk of configuration (API keys, sender identities, provider selection) lives in the provider adapter you choose.
firefly:
notifications:
# Provider selection — supplied by the adapter modules you add (see table below).
email:
provider: sendgrid # or: resend
sms:
provider: twilio
push:
provider: firebase
observability:
metrics:
enabled: true # default; set false to skip NotificationMetrics registration| Property | Default | Description |
|---|---|---|
firefly.notifications.email.provider |
(none) | Selects the email EmailProvider adapter — sendgrid or resend. Defined by the adapter module. |
firefly.notifications.sms.provider |
(none) | Selects the SMS SMSProvider adapter — twilio. Defined by the adapter module. |
firefly.notifications.push.provider |
(none) | Selects the push PushProvider adapter — firebase. Defined by the adapter module. |
firefly.observability.metrics.enabled |
true |
When true (and a MeterRegistry is present), registers the shared NotificationMetrics bean used by all adapters. |
Provider-specific keys (for example firefly.notifications.sendgrid.*, firefly.notifications.twilio.*, firefly.notifications.firebase.*) are documented in each adapter's README.
Template engine — FreemarkerNotificationTemplateEngine is not auto-registered; declare it as a bean. Its constructor accepts a classpath prefix (default /notification-templates) and an optional filesystem directory for template overrides. Without a NotificationTemplateEngine bean, sendTemplateEmail fails fast with an UnsupportedOperationException.
Notification preferences — InMemoryNotificationPreferenceService is suitable for development and single-instance deployments. For multi-instance/production usage, provide your own NotificationPreferenceService backed by R2DBC or a cache.
ARCHITECTURE.md— in-depth hexagonal architecture guide: ports & adapters, layer responsibilities, dependency flow, and how to add a new provider.- Firefly Framework Module Catalog — the full set of framework modules and adapters.
- Adapter READMes: SendGrid · Resend · Twilio · Firebase.
Contributions are welcome. Please read the CONTRIBUTING.md guide for details on our code of conduct, development process, and how to submit pull requests.
Copyright 2024-2026 Firefly Software Foundation.
Licensed under the Apache License, Version 2.0. See LICENSE for details.