NotifyHub
One API. Every channel.
Unified notification library for Java and Spring Boot.
Website Β Β·Β
Documentation Β Β·Β
Getting Started
Stop writing different code for each notification channel. NotifyHub gives you a single fluent API to send notifications via Email, SMS, WhatsApp, Slack, Telegram, Discord, Microsoft Teams, Firebase Push, Webhooks, WebSocket, Google Chat, Twitter/X, LinkedIn, Notion, Twitch, YouTube, Instagram, SendGrid, TikTok Shop, Facebook, AWS SNS, Mailgun, PagerDuty, Kick β or any custom channel you create.
notify.to(user)
.via(EMAIL)
.fallback(SMS)
.priority(Priority.HIGH)
.subject("Order confirmed" )
.template("order-confirmed" )
.param("orderId" , order.getId())
.attach(invoicePdf)
.send();
Why NotifyHub?
Problem Without NotifyHub With NotifyHub Email JavaMail config, MIME types, Session... .via(EMAIL)π± SMS Twilio SDK, different API entirely .via(SMS)WhatsApp Another Twilio setup, prefix logic .via(WHATSAPP)Slack Webhook HTTP, JSON payload .via(SLACK)Telegram Bot API, HTTP client setup .via(TELEGRAM)Discord Webhook HTTP, JSON payload .via(DISCORD)π₯ Teams Incoming Webhook, MessageCard JSON .via(TEAMS)Push Firebase Admin SDK, credentials... .via(PUSH)π Webhook Custom HTTP, payload template .via(Channel.custom("pagerduty"))WebSocket Java WebSocket API, reconnect logic .via(WEBSOCKET)Google Chat Webhook HTTP, JSON payload .via(GOOGLE_CHAT)Twitter/X OAuth 1.0a, API v2 setup .via(TWITTER)πΌ LinkedIn OAuth 2.0, REST API setup .via(LINKEDIN)Notion Integration Token, API setup .via(NOTION)Twitch OAuth 2.0, Twitch API setup .via(TWITCH)YouTube YouTube Data API v3 setup .via(YOUTUBE)Instagram Meta Graph API setup .via(INSTAGRAM)π§ SendGrid SendGrid API, webhook tracking .via(Channel.custom("sendgrid"))TikTok Shop HMAC-SHA256, Shop API .via(TIKTOK_SHOP)Facebook Graph API, Page tokens .via(FACEBOOK)βοΈ AWS SNS AWS SDK, credentials, ARN .via(Channel.custom("aws-sns"))Mailgun Mailgun API, domain setup .via(Channel.custom("mailgun"))PagerDuty Events API v2, routing key .via(Channel.custom("pagerduty"))Kick Public API, OAuth 2.1 .via(KICK)Multiple channels Completely different code for each Same fluent API Fallback Manual try/catch chain .fallback(SMS)Retry Implement yourself Built-in exponential backoff Async Thread pools, CompletableFuture .sendAsync()Scheduling ScheduledExecutor, timer logic .schedule(Duration.ofMinutes(30))Templates Each channel has its own engine One template, all channels i18n Manual locale resolution .locale(Locale.PT_BR)Rate limiting Token bucket from scratch Config-driven per-channel Tracking Build your own delivery log Built-in receipts + JPA Dead letters Lost in the void Auto-captured in DLQ Deduplication Track sent messages yourself Built-in content hash / explicit key Template versions Manage files manually .templateVersion("v2") + A/B testBatch Loop and pray .toAll(users).send()Monitoring Wire Micrometer yourself Auto-configured counters Health checks Write an Actuator indicator Auto-configured Admin UI Build your own dashboard Built-in /notify-admin Circuit breaker Implement yourself per channel Built-in per-channel circuit breaker Orchestration Manual escalation logic .orchestrate().first(EMAIL).ifNoOpen(24h).then(PUSH)A/B testing External service + glue code Built-in .abTest("exp").variant(...).split(50,50) Testing Mock everything TestNotifyHub captures all sendsNew channel Build from scratch Implement one interface
Table of Contents
Quick Start
1. Add the dependency
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-spring-boot-starter</artifactId>
<version>1.0 .0 </version>
</dependency>
Need extra channels? Add optional modules:
<!-- SMS + WhatsApp (Twilio) -->
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-sms</artifactId>
<version>1.0 .0 </version>
</dependency>
<!-- Slack / Telegram / Discord / Teams / Firebase Push / Webhook -->
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-slack</artifactId>
<version>1.0 .0 </version>
</dependency>
<!-- WebSocket / Google Chat -->
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-websocket</artifactId>
<version>1.0 .0 </version>
</dependency>
2. Configure in application.yml
notify:
channels:
email:
host: smtp.gmail.com
port: 587
username: ${GMAIL_USER}
password: ${GMAIL_PASS}
from : noreply@myapp.com
from -name: MyApp
tls: true
retry:
max-attempts: 3
strategy: exponential
tracking:
enabled: true
3. Inject and use
@Service
public class OrderService {
private final NotifyHub notify;
public OrderService(NotifyHub notify) {
this.notify = notify;
}
public void confirmOrder(Order order) {
notify.to(order.getCustomer())
.via(Channel.EMAIL)
.subject("Order confirmed!" )
.template("order-confirmed" )
.param("customerName" , order.getCustomer().getName())
.param("orderId" , order.getId())
.param("total" , order.getTotal())
.send();
}
}
That's it. Three steps.
Features
Fallback Chain
If the primary channel fails, automatically try the next one:
notify.to(user)
.via(Channel.WHATSAPP)
.fallback(Channel.SMS)
.fallback(Channel.EMAIL)
.template("payment-reminder" )
.param("amount" , "R$ 150,00" )
.send();
// Tries WhatsApp -> SMS -> Email
Multi-Channel Send
Send through ALL channels simultaneously:
notify.to(user)
.via(Channel.EMAIL)
.via(Channel.SLACK)
.via(Channel.TEAMS)
.subject("Security Alert" )
.content("Login from a new device detected" )
.sendAll();
Async Sending
Send notifications without blocking:
// Fire and forget
notify.to(user)
.via(Channel.EMAIL)
.template("welcome" )
.sendAsync();
// Or wait for result
CompletableFuture<Void> future = notify.to(user)
.via(Channel.EMAIL)
.via(Channel.SLACK)
.content("Deploy complete!" )
.sendAllAsync();
future.thenRun(() -> log.info("All notifications sent!" ));
Retry with Backoff
Automatic retry with exponential or fixed backoff:
# application.yml (global)
notify:
retry:
max-attempts: 3
strategy: exponential # waits 1s, 2s, 4s...
// Or per-notification
notify.to(user)
.via(Channel.EMAIL)
.retry(3 )
.template("invoice" )
.send();
Templates (Mustache)
Create templates in src/main/resources/templates/notify/:
order-confirmed.html (auto-used for email):
<h1>Hello, {{customerName}}!</h1>
<p>Your order <strong>#{{orderId}}</strong> has been confirmed.</p>
<p>Total: <strong>{{total}}</strong></p>
order-confirmed.txt (auto-used for SMS/WhatsApp/Slack/Telegram/Discord/Teams):
Hello {{customerName}}, your order #{{orderId}} is confirmed. Total: {{total}}
The library picks .html for email and .txt for other channels automatically.
i18n (Internationalization)
Templates support locale-based resolution with automatic fallback:
// User with locale
notify.to(user)
.via(Channel.EMAIL)
.locale(Locale.forLanguageTag("pt-BR" ))
.template("welcome" )
.param("name" , user.getName())
.send();
Template resolution order: welcome_pt_BR.html -> welcome_pt.html -> welcome.html
Your Notifiable can also return a locale:
public class User implements Notifiable {
@Override
public Locale getLocale() {
return Locale.forLanguageTag("pt-BR" );
}
}
Attachments
Attach files to email notifications:
notify.to(user)
.via(Channel.EMAIL)
.subject("Your Invoice" )
.template("invoice" )
.attach("invoice.pdf" , pdfBytes, "application/pdf" )
.attach(new File("/reports/monthly.xlsx" ))
.attach(Attachment.fromFile(contractFile))
.send();
Priority Levels
Set notification priority. URGENT notifications bypass rate limiting:
notify.to(user)
.via(Channel.EMAIL)
.priority(Priority.URGENT)
.subject("SERVER DOWN!" )
.content("Production server is unresponsive" )
.send();
Available priorities: URGENT (bypasses rate limits), HIGH, NORMAL (default), LOW.
Rate Limiting
Control notification throughput per-channel:
notify:
rate-limit:
enabled: true
max-requests: 100
window: 1m
channels:
email:
max-requests: 50
window: 1m
sms:
max-requests: 10
window: 1m
Rate limiting uses a token bucket algorithm. URGENT priority notifications always bypass rate limits.
Dead Letter Queue (DLQ)
Failed notifications (after all retries) are automatically captured in the DLQ:
notify:
tracking:
enabled: true
dlq-enabled: true
View and manage dead letters via the admin dashboard at /notify-admin/dlq, or programmatically:
DeadLetterQueue dlq = hub.getDeadLetterQueue();
List<DeadLetter> failed = dlq.findAll();
dlq.remove(deadLetterId); // after manual reprocessing
Batch Send
Send notifications to multiple recipients at once:
// By email addresses
notify.toAll(List.of("user1@test.com" , "user2@test.com" , "user3@test.com" ))
.via(Channel.EMAIL)
.subject("System Maintenance" )
.template("maintenance-notice" )
.param("date" , "2025-03-01" )
.send();
// By Notifiable entities
notify.toAllNotifiable(users)
.via(Channel.EMAIL)
.template("newsletter" )
.send();
// Async batch
notify.toAll(recipients)
.via(Channel.EMAIL)
.template("promo" )
.sendAsync();
Delivery Tracking
Track every notification with delivery receipts:
notify:
tracking:
enabled: true
type : memory # or "jpa" for database persistence
// Send and get a receipt
DeliveryReceipt receipt = notify.to(user)
.via(Channel.EMAIL)
.content("Hello!" )
.sendTracked();
System.out.println(receipt.getStatus()); // SENT
System.out.println(receipt.getId()); // uuid
System.out.println(receipt.getTimestamp()); // 2025-01-15T10:30:00Z
For database persistence, add the JPA tracker module:
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-tracker-jpa</artifactId>
<version>1.0 .0 </version>
</dependency>
notify:
tracking:
enabled: true
type : jpa
Scheduled Notifications
Schedule notifications for future delivery:
ScheduledNotification scheduled = notify.to(user)
.via(Channel.EMAIL)
.subject("Reminder" )
.content("Don't forget your appointment tomorrow!" )
.schedule(Duration.ofHours(24 ));
// Check status
scheduled.getStatus(); // SCHEDULED, SENT, FAILED, CANCELLED
scheduled.getRemainingDelay(); // PT23H59M...
// Cancel if needed
scheduled.cancel();
Notification Routing
Auto-route notifications based on user preferences:
public class User implements Notifiable {
@Override
public List<Channel> getPreferredChannels() {
return List.of(Channel.WHATSAPP, Channel.SMS, Channel.EMAIL);
}
}
// Auto-routes: WhatsApp (primary) -> SMS (fallback) -> Email (fallback)
notify.notify(user)
.template("order-update" )
.param("orderId" , "12345" )
.send();
Conditional routing with rules:
NotificationRouter router = NotificationRouter.builder()
.rule(RoutingRule.timeBasedRule(
LocalTime.of(9 , 0 ), LocalTime.of(18 , 0 ),
Channel.SLACK, Channel.EMAIL)) // Slack during business hours, email after
.build();
Notifiable Interface
Make your User entity a notification recipient:
@Entity
public class User implements Notifiable {
private String name;
private String email;
private String phone;
@Override
public String getNotifyEmail() { return email; }
@Override
public String getNotifyPhone() { return phone; }
@Override
public String getNotifyName() { return name; }
@Override
public Locale getLocale() { return Locale.forLanguageTag("pt-BR" ); }
@Override
public List<Channel> getPreferredChannels() {
return List.of(Channel.EMAIL, Channel.SMS);
}
}
Then just pass the user object:
notify.to(user) // resolves email/phone automatically
.via(Channel.EMAIL)
.template("welcome" )
.send();
Or use raw addresses:
notify.to("user@email.com" ).via(Channel.EMAIL).content("Hello!" ).send();
notify.toPhone("+5511999999999" ).via(Channel.SMS).content("Code: 1234" ).send();
Message Deduplication
Prevent duplicate notifications automatically with content hashing or explicit keys:
notify:
deduplication:
enabled: true
ttl: 24h
strategy: content-hash # content-hash | explicit-key | both
// Auto-dedup by content hash (same recipient + channel + content = skipped)
notify.to(user).via(EMAIL).content("Order confirmed" ).send();
notify.to(user).via(EMAIL).content("Order confirmed" ).send(); // skipped!
// Dedup by explicit key
notify.to(user).via(EMAIL)
.deduplicationKey("order-" + orderId)
.template("order-confirmed" )
.send();
Strategies:
content-hash β SHA-256 hash of recipient + channel + subject + content
explicit-key β uses the key provided via .deduplicationKey("...")
both β uses explicit key if provided, otherwise falls back to content hash
Without Spring Boot:
NotifyHub notify = NotifyHub.builder()
.deduplicationStore(new InMemoryDeduplicationStore(Duration.ofHours(12 )))
.channel(emailChannel)
.build();
Template Versioning
Manage multiple versions of templates for A/B testing or gradual rollouts:
templates/notify/
βββ order-confirmed.html β default version
βββ order-confirmed@v1.html β version v1
βββ order-confirmed@v2.html β version v2
βββ order-confirmed_pt_BR@v2.html β v2 with i18n
βββ order-confirmed.txt β text default
// Use a specific version
notify.to(user).via(EMAIL)
.template("order-confirmed" )
.templateVersion("v2" )
.param("orderId" , "123" )
.send();
// No version = default template (backward compatible)
notify.to(user).via(EMAIL)
.template("order-confirmed" )
.send();
// A/B testing
String version = abTestService.getVariant(user, "email-template" );
notify.to(user).via(EMAIL)
.template("welcome" )
.templateVersion(version) // "v1" or "v2"
.send();
Resolution order: {name}@{version}_{locale}.{variant} β {name}@{version}.{variant} β {name}_{locale}.{variant} β {name}.{variant}
Custom Channels
Create your own channel by implementing one interface:
@Component
public class PushChannel implements NotificationChannel {
@Override
public String getName() { return "push" ; }
@Override
public void send(Notification notification) {
firebaseClient.send(notification.getRecipient(), notification.getRenderedContent());
}
@Override
public boolean isAvailable() { return true ; }
}
Use it:
notify.to(user)
.via(Channel.custom("push" ))
.template("new-message" )
.send();
Spring Boot auto-discovers any NotificationChannel bean. No extra config needed.
Event Listeners + Spring Events
Monitor notification outcomes with the listener interface:
@Component
public class NotifyMonitor implements NotificationListener {
@Override
public void onSuccess(String channel, String template) {
metrics.increment("notifications.sent." + channel);
}
@Override
public void onFailure(String channel, String template, Exception error) {
log.error("Failed on {}: {}" , channel, error.getMessage());
alertService.warn("Channel " + channel + " is failing" );
}
@Override
public void onScheduled(String channel, String recipient, Duration delay) {
log.info("Scheduled for {} in {}" , recipient, delay);
}
}
Or use Spring Application Events (auto-configured):
@Component
public class NotificationEventHandler {
@EventListener
public void onSent(NotificationSentEvent event) {
log.info("Sent via {} to {}" , event.getChannel(), event.getRecipient());
}
@EventListener
public void onFailed(NotificationFailedEvent event) {
log.error("Failed: {}" , event.getError().getMessage());
}
}
Named Recipients
Send notifications to multiple destinations per channel using named aliases. Instead of one hardcoded webhook URL or chat ID, configure as many as you need:
Configure in application.yml:
notify:
channels:
discord:
webhook-url: ${DISCORD_DEFAULT} # default destination
username: NotifyHub
avatar-url: https://example.com/logo.png
recipients:
alerts: https://discord.com/api/webhooks/111/aaa
devops: https://discord.com/api/webhooks/222/bbb
general: https://discord.com/api/webhooks/333/ccc
slack:
webhook-url: ${SLACK_DEFAULT}
recipients:
engineering: https://hooks.slack.com/services/XXX/YYY/ZZZ
marketing: https://hooks.slack.com/services/AAA/BBB/CCC
telegram:
bot-token: ${TELEGRAM_BOT_TOKEN}
chat-id: ${TELEGRAM_DEFAULT_CHAT}
recipients:
alerts: "-1001234567890"
devops: "-1009876543210"
Use with the Java API:
// Send to a named alias
notify.to("alerts" ).via(DISCORD).content("Server is down!" ).send();
notify.to("engineering" ).via(SLACK).content("Deploy complete" ).send();
notify.to("devops" ).via(TELEGRAM).content("CPU at 95%" ).send();
// Send to default (no alias)
notify.to("user" ).via(DISCORD).content("Hello!" ).send();
// Pass a raw URL directly (no alias needed)
notify.to("https://discord.com/api/webhooks/444/ddd" ).via(DISCORD).content("Direct!" ).send();
Use with the MCP Server (AI Agents):
send_discord(recipient="alerts" , body="Server is down!" )
send_slack(recipient="engineering" , body="Deploy complete" )
send_telegram(recipient="devops" , body="CPU at 95%" )
Environment variables for MCP/Docker:
# Default webhook
NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/111/aaa
# Named recipients (RECIPIENTS_<NAME>)
NOTIFY_CHANNELS_DISCORD_RECIPIENTS_ALERTS=https://discord.com/api/webhooks/222/bbb
NOTIFY_CHANNELS_DISCORD_RECIPIENTS_DEVOPS=https://discord.com/api/webhooks/333/ccc
# Same pattern for all channels
NOTIFY_CHANNELS_SLACK_RECIPIENTS_ENGINEERING=https://hooks.slack.com/services/XXX
NOTIFY_CHANNELS_TELEGRAM_RECIPIENTS_ALERTS=-1001234567890
NOTIFY_CHANNELS_TEAMS_RECIPIENTS_GENERAL=https://outlook.office.com/webhook/XXX
NOTIFY_CHANNELS_GOOGLE_CHAT_RECIPIENTS_TEAM=https://chat.googleapis.com/v1/spaces/XXX
Resolution order: alias match in recipients map > raw URL/value passthrough > default from config.
Supported on: Discord, Slack, Telegram, Teams, Google Chat .
Message Queue (RabbitMQ / Kafka)
Decouple notification sending with async message queues. NotifyHub provides two modules:
RabbitMQ
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-queue-rabbitmq</artifactId>
<version>1.0 .0 </version>
</dependency>
spring.rabbitmq.host: localhost
spring.rabbitmq.port: 5672
notify.queue.rabbitmq:
enabled: true
queue-name: notifyhub-notifications
exchange-name: notifyhub-exchange
routing-key: notification
consumer:
enabled: true
concurrency: 1
max-concurrency: 5
@Autowired RabbitNotificationProducer producer;
// Enqueue for async delivery
producer.enqueue(QueuedNotification.builder()
.recipient("user@example.com" )
.channelName("email" )
.subject("Welcome!" )
.templateName("welcome" )
.params(Map.of("name" , "Gabriel" ))
.build());
// Consumer picks it up and sends via NotifyHub automatically
Apache Kafka
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-queue-kafka</artifactId>
<version>1.0 .0 </version>
</dependency>
spring.kafka.bootstrap-servers: localhost:9092
notify.queue.kafka:
enabled: true
topic: notifyhub-notifications
consumer:
enabled: true
group-id: notifyhub-group
concurrency: 1
@Autowired KafkaNotificationProducer producer;
// Same API as RabbitMQ β just different transport
producer.enqueue(QueuedNotification.builder()
.recipient("+5548999999999" )
.channelName("sms" )
.rawContent("Your code is 1234" )
.priority("URGENT" )
.build());
Both modules support: templates, priority, deduplication keys, delivery tracking, and phone number routing for SMS/WhatsApp.
Circuit Breaker
Per-channel circuit breaker prevents cascading failures. If a channel fails repeatedly, the circuit opens and short-circuits further attempts:
// Without Spring Boot
NotifyHub notify = NotifyHub.builder()
.channel(emailChannel)
.circuitBreaker(CircuitBreakerConfig.defaults()) // 5 failures β open for 30s
.build();
// Custom thresholds
NotifyHub notify = NotifyHub.builder()
.channel(emailChannel)
.circuitBreaker(CircuitBreakerConfig.custom()
.failureThreshold(3 )
.openDuration(Duration.ofMinutes(1 ))
.windowSize(Duration.ofSeconds(30 ))
.build())
.build();
# Spring Boot
notify:
circuit-breaker:
enabled: true
failure-threshold: 5
open-duration: 30s
window-size: 60s
States: CLOSED (normal) β OPEN (rejecting) β HALF_OPEN (testing recovery). The health endpoint includes circuit breaker status per channel.
Bulkhead (Concurrency Isolation)
Limit concurrent sends per channel to prevent resource exhaustion:
NotifyHub notify = NotifyHub.builder()
.channel(emailChannel)
.bulkhead(BulkheadConfig.defaults()) // 10 concurrent per channel
.bulkhead(BulkheadConfig.perChannel(5 )) // or custom limit
.build();
Multi-Channel Orchestration
Build escalation workflows that promote through channels if the user doesn't engage:
notify.to(user)
.orchestrate()
.first(Channel.EMAIL)
.template("order-update" )
.ifNoOpen(Duration.ofHours(24 ))
.then(Channel.PUSH)
.content("You have an unread order update" )
.ifNoOpen(Duration.ofHours(48 ))
.then(Channel.SMS)
.content("Order update waiting β check your email" )
.execute();
Each step waits for the specified duration before escalating to the next channel.
A/B Testing
Built-in deterministic A/B testing for notifications. Variant assignment is hash-based (SHA-256) β the same recipient always gets the same variant:
notify.to(user)
.via(Channel.EMAIL)
.subject("Welcome!" )
.abTest("welcome-experiment" )
.variant("control" , b -> b.template("welcome-v1" ))
.variant("new-design" , b -> b.template("welcome-v2" ))
.split(50 , 50 );
Supports any number of variants with weighted splits. Deterministic hashing ensures consistent experiences across sends.
Cron Scheduling
Schedule recurring notifications with cron expressions:
ScheduledNotification job = notify.to(user)
.via(Channel.EMAIL)
.template("weekly-digest" )
.cron("0 9 * * MON" ); // Every Monday at 9 AM
Supports standard 5-field cron syntax: minute, hour, day-of-month, month, day-of-week. Includes ranges, lists, steps, and named days/months.
Quiet Hours
Respect user preferences for notification timing:
public class User implements Notifiable {
@Override
public QuietHours getQuietHours() {
return QuietHours.between(
LocalTime.of(22 , 0 ), // 10 PM
LocalTime.of(8 , 0 ), // 8 AM
ZoneId.of("America/Sao_Paulo" )
);
}
@Override
public Set<Channel> getOptedOutChannels() {
return Set.of(Channel.SMS); // User opted out of SMS
}
}
Notifications sent during quiet hours are delayed to the next allowed window. Opted-out channels are silently skipped.
Testing Utilities
TestNotifyHub provides a test-friendly wrapper with capturing channels for all built-in channel types:
@Test
void shouldSendWelcomeEmail() {
TestNotifyHub test = TestNotifyHub.create();
test.to("user@test.com" )
.via(Channel.EMAIL)
.subject("Welcome" )
.content("Hello!" )
.send();
assertThat(test.sent()).hasSize(1 );
assertThat(test.sent("email" ).get(0 ).subject()).isEqualTo("Welcome" );
}
No mocking needed β TestNotifyHub captures all notifications in memory for assertions. Call test.reset() between tests.
Supported Channels
Channel Provider Module Email SMTP (Gmail, SES, Outlook, any) notify-emailπ± SMS Twilio notify-smsWhatsApp Twilio notify-smsSlack Incoming Webhooks notify-slackTelegram Bot API notify-telegramDiscord Webhooks notify-discordπ₯ Microsoft Teams Incoming Webhooks notify-teamsPush (FCM) Firebase Cloud Messaging notify-push-firebaseπ Webhook Any HTTP endpoint notify-webhookWebSocket JDK WebSocket (java.net.http) notify-websocketGoogle Chat Webhooks notify-google-chatTwitter/X API v2 (OAuth 1.0a) notify-twitterπΌ LinkedIn REST API (OAuth 2.0) notify-linkedinNotion API (Integration Token) notify-notionTwitch Helix API (OAuth 2.0 auto-refresh) notify-twitchYouTube Data API v3 (OAuth auto-refresh) notify-youtubeInstagram Meta Graph API notify-instagramπ§ SendGrid SendGrid API (delivery tracking) notify-sendgridTikTok Shop TikTok Shop API (HMAC-SHA256) notify-tiktok-shopFacebook Graph API (Page + Messenger) notify-facebookWhatsApp Cloud Meta Cloud API (direct, no Twilio) notify-whatsappβοΈ AWS SNS AWS SDK v2 notify-aws-snsMailgun Mailgun REST API notify-mailgunPagerDuty Events API v2 notify-pagerdutyKick Public API notify-kickβ Custom Any β implement one interface notify-core
Admin Dashboard
NotifyHub includes a built-in admin dashboard for monitoring your notification system.
notify:
admin:
enabled: true
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-admin</artifactId>
<version>1.0 .0 </version>
</dependency>
Access at /notify-admin to see:
Dashboard β metric cards (sent/failed/pending/DLQ/channels/contacts), recent activity feed, system status grid, registered channels
Analytics β Chart.js charts with send volume, channel distribution, success rate, hourly heatmap
Tracking β delivery receipts with channel filter and status badges
Dead Letter Queue β failed notifications with error details and remove action
Channels β status of each registered channel with health indicators
Audit Log β complete history of all notification events with event type filter
Audiences β manage contacts and audience segments with tag-based filtering
Status Webhook β real-time HTTP callback configuration and delivery history
Dark/light theme toggle included β persists across pages via localStorage.
Spring Boot Integration
Micrometer Metrics
Auto-configured when Micrometer is on the classpath:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
Exposes counters and gauges via the unified EventBus:
notifyhub.notifications.sent (tags: channel)
notifyhub.notifications.failed (tags: channel)
notifyhub.notifications.retried (tags: channel)
notifyhub.notifications.rate_limited (tags: channel)
notifyhub.notifications.deduped (tags: channel)
notifyhub.notifications.circuit_opened (tags: channel)
notifyhub.notifications.circuit_closed (tags: channel)
notifyhub.notifications.send_duration (timer, tags: channel)
Actuator Health Check
Auto-configured when Spring Boot Actuator is on the classpath:
GET /actuator/health/notifyhub
{
"status" : "UP" ,
"details" : {
"email" : { "status" : "UP" , "circuitBreaker" : "CLOSED" },
"slack" : { "status" : "UP" , "circuitBreaker" : "CLOSED" },
"totalChannels" : 2 ,
"availableChannels" : 2
}
}
Status: UP (all channels available), DEGRADED (some down), DOWN (all down). When circuit breaker is configured, each channel also reports its circuit state.
Actuator Info
{
"notifyhub" : {
"version" : "1.0.0" ,
"channels" : ["email" , "slack" , "teams" ],
"tracking.enabled" : true ,
"dlq.enabled" : true
}
}
OpenTelemetry Tracing
Auto-configured when Micrometer Observation and an OTel bridge are on the classpath.
Add these dependencies to export distributed traces via OTLP:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
Configure the OTLP endpoint in application.yml:
management:
tracing:
sampling:
probability: 1.0 # 100 % sampling (adjust for production)
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
Creates observations (spans):
notifyhub.send (tags: channel, template, outcome)
notifyhub.schedule (tags: channel, outcome)
Compatible with Jaeger, Zipkin, Grafana Tempo, Datadog, and any OTLP-compatible collector.
Webhook HMAC Signing
The Status Webhook listener supports HMAC-SHA256 request signing for security. When configured, every webhook POST includes a X-NotifyHub-Signature header that your server can use to verify the request came from NotifyHub.
notify:
status-webhook:
url: https://your-server.com/webhook
signing-secret: ${WEBHOOK_SECRET} # any secret string
Each request includes the header:
X-NotifyHub-Signature: sha256=<hex-encoded HMAC-SHA256 of request body>
Verify in your server:
Mac mac = Mac.getInstance("HmacSHA256" );
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256" ));
String expected = "sha256=" + HexFormat.of().formatHex(mac.doFinal(body.getBytes()));
boolean valid = MessageDigest.isEqual(expected.getBytes(), signature.getBytes());
Configuration Reference
Full application.yml with all options:
notify:
channels:
email:
host: smtp.gmail.com
port: 587
username: ${GMAIL_USER}
password: ${GMAIL_PASS}
from : noreply@myapp.com
from -name: MyApp
tls: true
ssl: false
sms:
account-sid: ${TWILIO_SID}
auth-token: ${TWILIO_TOKEN}
from -number : "+1234567890"
whatsapp:
account-sid: ${TWILIO_SID}
auth-token: ${TWILIO_TOKEN}
from -number : "+14155238886"
slack:
webhook-url: ${SLACK_WEBHOOK}
recipients: # named aliases (optional)
engineering: https://hooks.slack.com/services/XXX
marketing: https://hooks.slack.com/services/YYY
telegram:
bot-token: ${TELEGRAM_BOT_TOKEN}
chat-id: ${TELEGRAM_CHAT_ID}
recipients: # named aliases (optional)
alerts: "-1001234567890"
devops: "-1009876543210"
discord:
webhook-url: ${DISCORD_WEBHOOK}
username: NotifyHub
avatar-url: https://example.com/logo.png
recipients: # named aliases (optional)
alerts: https://discord.com/api/webhooks/111/aaa
devops: https://discord.com/api/webhooks/222/bbb
teams:
webhook-url: ${TEAMS_WEBHOOK}
recipients: # named aliases (optional)
general: https://outlook.office.com/webhook/XXX
push:
credentials-path: ${FIREBASE_CREDENTIALS}
project-id: ${FIREBASE_PROJECT_ID}
webhooks:
- name: pagerduty
url: https://events.pagerduty.com/v2/enqueue
headers:
Authorization: "Token ${PAGERDUTY_TOKEN}"
payload-template: '{"summary":"{{content}}"}'
websocket:
uri: wss://echo.example.com/ws
timeout-ms: 10000
reconnect-enabled: true
reconnect-delay-ms: 5000
max-reconnect-attempts: 3
headers:
Authorization: "Bearer ${WS_TOKEN}"
message-format: '{"type":"notification","content":"{{content}}"}'
google-chat:
webhook-url: ${GOOGLE_CHAT_WEBHOOK}
timeout-ms: 10000
recipients: # named aliases (optional)
team-a: https://chat.googleapis.com/v1/spaces/XXX/messages?key=YYY
team-b: https://chat.googleapis.com/v1/spaces/ZZZ/messages?key=WWW
retry:
max-attempts: 3
strategy: exponential
rate-limit:
enabled: true
max-requests: 100
window: 1m
channels:
email:
max-requests: 50
window: 1m
tracking:
enabled: true
type : memory # memory | jpa
dlq-enabled: true
deduplication:
enabled: true
ttl: 24h
strategy: content-hash # content-hash | explicit-key | both
admin:
enabled: true
Without Spring Boot
NotifyHub works without Spring β use the builder directly:
NotifyHub notify = NotifyHub.builder()
.templateEngine(new MustacheTemplateEngine())
.channel(new SmtpEmailChannel(
SmtpConfig.builder()
.host("smtp.gmail.com" ).port(587 )
.username("user@gmail.com" ).password("app-password" )
.from ("noreply@myapp.com" ).tls(true )
.build()
))
.channel(new SlackChannel(
SlackConfig.builder()
.webhookUrl("https://hooks.slack.com/services/XXX/YYY/ZZZ" )
.recipients(Map.of("engineering" , "https://hooks.slack.com/services/AAA/BBB/CCC" ))
.build()
))
.channel(new TeamsChannel(
TeamsConfig.builder()
.webhookUrl("https://outlook.office.com/webhook/XXX/YYY/ZZZ" )
.build()
))
.channel(new WebhookChannel(
WebhookConfig.builder()
.name("pagerduty" )
.url("https://events.pagerduty.com/v2/enqueue" )
.payloadTemplate("{\"summary\":\"{{content}}\"}" )
.build()
))
.channel(new WebSocketChannel(
WebSocketConfig.builder()
.uri("wss://echo.example.com/ws" )
.messageFormat("{\"text\":\"{{content}}\"}" )
.build()
))
.channel(new GoogleChatChannel(
GoogleChatConfig.builder()
.webhookUrl("https://chat.googleapis.com/v1/spaces/XXX/messages?key=YYY" )
.build()
))
.deduplicationStore(new InMemoryDeduplicationStore(Duration.ofHours(24 )))
.defaultRetryPolicy(RetryPolicy.exponential(3 ))
.rateLimiter(new TokenBucketRateLimiter(
RateLimitConfig.perMinute(100 )))
.deadLetterQueue(new InMemoryDeadLetterQueue())
.tracker(new InMemoryNotificationTracker())
.circuitBreaker(CircuitBreakerConfig.defaults())
.bulkhead(BulkheadConfig.defaults())
.build();
// Sync
notify.to("user@email.com" )
.via(Channel.EMAIL)
.subject("Hello!" )
.content("Welcome to the app!" )
.send();
// Async
notify.to("#general" )
.via(Channel.SLACK)
.content("Deploy complete!" )
.sendAsync();
// Tracked
DeliveryReceipt receipt = notify.to(user)
.via(Channel.EMAIL)
.content("Invoice attached" )
.sendTracked();
// Scheduled
notify.to(user)
.via(Channel.EMAIL)
.content("Reminder!" )
.schedule(Duration.ofMinutes(30 ));
// Batch
notify.toAll(List.of("a@test.com" , "b@test.com" ))
.via(Channel.EMAIL)
.template("announcement" )
.send();
Only notify-core + channel modules needed. No Spring dependency.
MCP Server (AI Agents)
NotifyHub includes an MCP (Model Context Protocol) server that exposes all notification channels as tools for AI agents like Claude Desktop , Claude Code , Cursor , and any MCP-compatible client.
How it works
The notify-mcp module is a standalone Java application that communicates via STDIO using the JSON-RPC protocol. AI agents discover the available tools and can send notifications through any configured channel.
Setup
1. Build the MCP server:
mvn clean package -pl notify-mcp -am -DskipTests
2. Configure in Claude Desktop (claude_desktop_config.json):
{
"mcpServers" : {
"notify-hub" : {
"command" : "java" ,
"args" : ["-jar" , "path/to/notify-mcp-1.0.0.jar" ],
"env" : {
"NOTIFY_CHANNELS_EMAIL_HOST" : "smtp.gmail.com" ,
"NOTIFY_CHANNELS_EMAIL_PORT" : "587" ,
"NOTIFY_CHANNELS_EMAIL_USERNAME" : "you@gmail.com" ,
"NOTIFY_CHANNELS_EMAIL_PASSWORD" : "app-password" ,
"NOTIFY_CHANNELS_SLACK_WEBHOOK_URL" : "https://hooks.slack.com/..." ,
"NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL" : "https://discord.com/api/webhooks/..."
}
}
}
}
Or for Claude Code (.mcp.json in project root):
{
"mcpServers" : {
"notify-hub" : {
"command" : "java" ,
"args" : ["-jar" , "path/to/notify-mcp-1.0.0.jar" ],
"env" : {
"NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL" : "https://discord.com/api/webhooks/..."
}
}
}
}
Available MCP Tools
Tool Description Required Params send_notificationSend via any channel (generic) channel, recipient, body or templatesend_emailSend email to, body or templatesend_smsSend SMS via Twilio phone, body or templatesend_slackSend to Slack channel recipient, body or templatesend_telegramSend via Telegram Bot recipient, body or templatesend_discordSend to Discord channel recipient, body or templatesend_whatsappSend WhatsApp via Twilio phone, body or templatesend_teamsSend to Microsoft Teams recipient, body or templatesend_google_chatSend to Google Chat recipient, body or templatesend_pushSend push via Firebase push_token, bodysend_twitterPost a tweet on Twitter/X body or templatesend_linkedinPublish a post on LinkedIn body or templatesend_notionCreate a page in Notion recipient, body or templatesend_twitchSend Twitch chat message + polls recipient, body or templatesend_youtubeSend YouTube live chat message recipient, body or templatesend_instagramSend Instagram DM or feed post recipient, body or templatesend_multi_channelSend to multiple channels channels[], recipient, body or templatesend_batchSend to multiple recipients at once recipients[], channel, body or templatesend_to_audienceSend to a named audience audience, channel, body or templatelist_channelsList configured channels (none) list_delivery_receiptsQuery delivery history (none) list_dead_lettersView failed notifications (DLQ) (none) create_contactCreate a contact with tags namelist_contactsList contacts (filter by tag) (none) create_audienceCreate audience with tag filters name, tags[]list_audiencesList audiences with contact counts (none) get_analyticsDelivery stats by channel/status (none) send_tiktok_shopSend TikTok Shop notification recipient, body or templatesend_facebookSend Facebook page post or Messenger DM recipient, body or templatecheck_email_statusCheck SendGrid email delivery status message_idschedule_notificationSchedule a notification for later delivery channel, recipient, body, send_atlist_scheduled_notificationsList all scheduled notifications (none) cancel_scheduled_notificationCancel a pending scheduled notification notification_id
All send tools optionally accept: subject, template, params, priority.
Usage example (from an AI agent)
Once configured, you can simply ask your AI agent:
"Send a Discord message to #alerts saying the deploy is complete"
The agent will call the send_discord tool with the appropriate parameters.
Docker
Run the MCP server without Java installed β only Docker required:
# Build
docker build -t notifyhub-mcp .
# Run with Discord
docker run -i --rm \
-e NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..." \
-e NOTIFY_CHANNELS_DISCORD_USERNAME="NotifyHub" \
gabrielbbal10/notifyhub-mcp
# Run with multiple Discord channels + Email
docker run -i --rm \
-e NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..." \
-e NOTIFY_CHANNELS_DISCORD_USERNAME="NotifyHub" \
-e NOTIFY_CHANNELS_DISCORD_RECIPIENTS_ALERTS="https://discord.com/api/webhooks/111/aaa" \
-e NOTIFY_CHANNELS_DISCORD_RECIPIENTS_DEVOPS="https://discord.com/api/webhooks/222/bbb" \
-e NOTIFY_CHANNELS_EMAIL_HOST="smtp.gmail.com" \
-e NOTIFY_CHANNELS_EMAIL_PORT="587" \
-e NOTIFY_CHANNELS_EMAIL_USERNAME="you@gmail.com" \
-e NOTIFY_CHANNELS_EMAIL_PASSWORD="app-password" \
-e NOTIFY_CHANNELS_EMAIL_FROM="you@gmail.com" \
gabrielbbal10/notifyhub-mcp
Configure in Claude Code (.mcp.json):
{
"mcpServers" : {
"notify-hub" : {
"command" : "docker" ,
"args" : ["run" , "-i" , "--rm" ,
"-e" , "NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/..." ,
"-e" , "NOTIFY_CHANNELS_DISCORD_USERNAME=NotifyHub" ,
"-e" , "NOTIFY_CHANNELS_DISCORD_RECIPIENTS_ALERTS=https://discord.com/api/webhooks/111/aaa" ,
"gabrielbbal10/notifyhub-mcp"
]
}
}
}
Docker REST API
Run a full REST API with Swagger UI β no Java required, just Docker:
docker run -d -p 8080 :8080 \
-e NOTIFY_CHANNELS_EMAIL_USERNAME=you@gmail.com \
-e NOTIFY_CHANNELS_EMAIL_PASSWORD=your-app-password \
-e NOTIFY_CHANNELS_EMAIL_FROM=you@gmail.com \
gabrielbbal10/notifyhub-api:latest
Open http://localhost:8080/swagger-ui.html for interactive API docs.
Environment variables β same for both MCP and API images, add only the channels you need:
Channel Variable Required Example Email NOTIFY_CHANNELS_EMAIL_HOSTNo (default: smtp.gmail.com) smtp.gmail.comNOTIFY_CHANNELS_EMAIL_PORTNo (default: 587) 587NOTIFY_CHANNELS_EMAIL_USERNAMEYes (for email) you@gmail.comNOTIFY_CHANNELS_EMAIL_PASSWORDYes (for email) abcd efgh ijkl mnop (App Password )NOTIFY_CHANNELS_EMAIL_FROMYes (for email) you@gmail.comDiscord NOTIFY_CHANNELS_DISCORD_WEBHOOK_URLYes (for discord) https://discord.com/api/webhooks/...NOTIFY_CHANNELS_DISCORD_USERNAMENo NotifyHubNOTIFY_CHANNELS_DISCORD_AVATAR_URLNo https://example.com/avatar.pngNOTIFY_CHANNELS_DISCORD_RECIPIENTS_<NAME>No Named alias webhook URL Slack NOTIFY_CHANNELS_SLACK_WEBHOOK_URLYes (for slack) https://hooks.slack.com/services/...NOTIFY_CHANNELS_SLACK_RECIPIENTS_<NAME>No Named alias webhook URL Telegram NOTIFY_CHANNELS_TELEGRAM_BOT_TOKENYes (for telegram) 123456:ABC-DEF...NOTIFY_CHANNELS_TELEGRAM_CHAT_IDYes (for telegram) 123456789NOTIFY_CHANNELS_TELEGRAM_RECIPIENTS_<NAME>No Named alias chat ID Google Chat NOTIFY_CHANNELS_GOOGLE_CHAT_WEBHOOK_URLYes (for gchat) https://chat.googleapis.com/v1/spaces/...NOTIFY_CHANNELS_GOOGLE_CHAT_RECIPIENTS_<NAME>No Named alias webhook URL Teams NOTIFY_CHANNELS_TEAMS_WEBHOOK_URLYes (for teams) https://outlook.office.com/webhook/...NOTIFY_CHANNELS_TEAMS_RECIPIENTS_<NAME>No Named alias webhook URL SMS NOTIFY_CHANNELS_SMS_ACCOUNT_SIDYes (for sms) Twilio Account SID NOTIFY_CHANNELS_SMS_AUTH_TOKENYes (for sms) Twilio Auth Token NOTIFY_CHANNELS_SMS_FROM_NUMBERYes (for sms) +12025551234WhatsApp NOTIFY_CHANNELS_WHATSAPP_ACCOUNT_SIDYes (for whatsapp) Twilio Account SID NOTIFY_CHANNELS_WHATSAPP_AUTH_TOKENYes (for whatsapp) Twilio Auth Token NOTIFY_CHANNELS_WHATSAPP_FROM_NUMBERYes (for whatsapp) +14155238886
Example with multiple channels:
docker run -d -p 8080 :8080 \
-e NOTIFY_CHANNELS_EMAIL_USERNAME=you@gmail.com \
-e NOTIFY_CHANNELS_EMAIL_PASSWORD=your-app-password \
-e NOTIFY_CHANNELS_EMAIL_FROM=you@gmail.com \
-e NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..." \
-e NOTIFY_CHANNELS_DISCORD_USERNAME="NotifyHub" \
-e NOTIFY_CHANNELS_TELEGRAM_BOT_TOKEN="123456:ABC-DEF..." \
-e NOTIFY_CHANNELS_TELEGRAM_CHAT_ID="123456789" \
-e NOTIFY_CHANNELS_GOOGLE_CHAT_WEBHOOK_URL="https://chat.googleapis.com/v1/spaces/..." \
gabrielbbal10/notifyhub-api:latest
Usage from any language:
# Send email
curl -X POST "http://localhost:8080/send/email?to=user@example.com&subject=Hello&body=Hi!"
# Send Discord
curl -X POST "http://localhost:8080/send/discord?message=Deploy done!"
# Send Telegram
curl -X POST "http://localhost:8080/send/telegram?chatId=123456789&message=Alert!"
# Send Google Chat
curl -X POST "http://localhost:8080/send/google-chat?message=Build passed!"
Docker Hub: gabrielbbal10/notifyhub-api
Running the Demo
The demo app showcases every feature with a built-in SMTP server β zero external config needed .
git clone https://github.com/GabrielBBaldez/notify-hub.git
cd notify-hub
mvn clean install -DskipTests
# Run the demo
mvn -pl notify-demo spring-boot:run
Then open:
Demo Endpoints
Method Endpoint Description GET/Home β lists all endpoints POST/send/emailSend a simple email POST/send/templateSend email with Mustache template POST/send/notifiableSend to a Notifiable entity POST/send/smsSend SMS (requires Twilio) POST/send/whatsappSend WhatsApp (requires Twilio) POST/send/telegramSend to Telegram via Bot POST/send/discordSend to Discord via Webhook POST/send/slackSend to Slack channel POST/send/teamsSend to Microsoft Teams via Webhook POST/send/google-chatSend to Google Chat via Webhook POST/send/pushSend push notification via Firebase POST/send/websocketSend message via WebSocket POST/send/multiSend to email + Slack simultaneously POST/send/fallbackTest fallback (email fails -> Slack) POST/send/trackedSend with delivery tracking POST/send/scheduledSchedule notification for future GET/trackingDelivery tracking history GET/notify-adminAdmin dashboard GET/inboxView captured emails DELETE/inboxClear all inboxes
Architecture
notify-hub/
βββ notify-core/ # Zero Spring dependency
β βββ NotifyHub # Thin facade β delegates to executor/scheduler/eventbus
β βββ NotificationExecutor # Channel resolution, send logic, fallback chains
β βββ NotificationScheduler # Scheduling with delay/cancel/list
β βββ NotificationBuilder # Fluent builder (send/async /tracked/scheduled)
β βββ BatchNotificationBuilder # Batch send to multiple recipients
β βββ Notification # Immutable notification object
β βββ Channel / ChannelRef # Built-in + custom channel refs
β βββ Priority # URGENT, HIGH, NORMAL, LOW
β βββ Notifiable # Recipient interface (i18n + routing + quiet hours)
β βββ NotificationChannel # Channel SPI (implement this!)
β βββ QuietHours # Per-recipient quiet time windows
β βββ pipeline/ # Resilience handler chain
β β βββ SendPipeline # Assembles: Dedup β RateLimit β CircuitBreaker β Template β Retry
β β βββ SendHandler # Handler chain interface
β β βββ SendContext # Per-send state (channel, builder, notification)
β β βββ DeduplicationHandler # Skip duplicates
β β βββ RateLimitHandler # Enforce per-channel limits
β β βββ CircuitBreakerHandler # Short-circuit failing channels
β β βββ TemplateHandler # Build + render notification
β β βββ RetrySendHandler # Terminal: retry with backoff β DLQ
β βββ resilience/ # Resilience primitives
β β βββ ChannelCircuitBreaker # Per-channel circuit breaker (sliding window)
β β βββ CircuitBreakerConfig # Thresholds + durations
β β βββ CircuitState # CLOSED, OPEN, HALF_OPEN
β β βββ BulkheadConfig # Per-channel concurrency limits
β βββ event/ # Unified event system
β β βββ NotificationEventBus # Publish events to listeners
β β βββ NotificationEvent # Immutable event record
β β βββ EventType # SENT, FAILED, RETRIED, RATE_LIMITED, DEDUPED, CIRCUIT_*
β β βββ NotificationEventListener # Listener interface
β β βββ LegacyListenerAdapter # Bridge: old NotificationListener β EventBus
β βββ orchestration/ # Multi-step notification workflows
β β βββ OrchestrationBuilder # first(EMAIL).ifNoOpen(24h).then(PUSH)
β β βββ OrchestrationStep # Individual step record
β βββ abtest/ # A/B testing
β β βββ AbTestBuilder # Deterministic SHA-256 variant selection
β βββ schedule/ # Cron support
β β βββ CronExpression # Lightweight 5 -field cron parser
β βββ attachment/ # File attachments
β β βββ Attachment # Email file attachments
β βββ testing/ # Test utilities
β β βββ TestNotifyHub # Test wrapper with capturing channels
β β βββ SentNotification # Captured notification record
β βββ RateLimiter / TokenBucket # Rate limiting
β βββ NotificationRouter / RoutingRule # Conditional routing
β βββ MustacheTemplateEngine # Template engine (i18n-aware + versioning)
β βββ DeduplicationStore # Dedup interface (in-memory impl)
β βββ RetryPolicy # Retry + backoff strategies
β
βββ notify-channels/
β βββ notify-email/ # SMTP email (Jakarta Mail + attachments)
β βββ notify-sms/ # Twilio SMS + WhatsApp
β βββ notify-slack/ # Slack webhooks (JDK HttpClient)
β βββ notify-telegram/ # Telegram Bot API (JDK HttpClient)
β βββ notify-discord/ # Discord webhooks (JDK HttpClient)
β βββ notify-teams/ # Microsoft Teams webhooks (JDK HttpClient)
β βββ notify-push-firebase/ # Firebase Cloud Messaging (FCM)
β βββ notify-webhook/ # Generic webhook (configurable)
β βββ notify-websocket/ # WebSocket (JDK java.net.http)
β βββ notify-google-chat/ # Google Chat webhooks (JDK HttpClient)
β βββ notify-twitch/ # Twitch chat + polls via Helix API (JDK HttpClient)
β βββ notify-youtube/ # YouTube live chat via Data API v3 (JDK HttpClient)
β βββ notify-instagram/ # Instagram DM + feed via Meta Graph API (JDK HttpClient)
β βββ notify-sendgrid/ # SendGrid email with delivery tracking (JDK HttpClient)
β βββ notify-tiktok-shop/ # TikTok Shop API (HMAC-SHA256, JDK HttpClient)
β βββ notify-facebook/ # Facebook Graph API (Page + Messenger)
β βββ notify-whatsapp/ # WhatsApp Cloud API (Meta Graph API, no Twilio)
β βββ notify-aws-sns/ # AWS SNS (AWS SDK v2)
β βββ notify-mailgun/ # Mailgun transactional email (JDK HttpClient)
β βββ notify-pagerduty/ # PagerDuty Events API v2 (JDK HttpClient)
β βββ notify-kick/ # Kick Public API (JDK HttpClient)
β βββ notify-channel-template/ # Template/archetype for creating new channels
β
βββ notify-tracker-jpa/ # JPA-backed delivery tracker
βββ notify-audit-jpa/ # JPA-backed audit logging
β
βββ notify-spring-boot-starter/ # Auto-config for Spring Boot
β βββ NotifyAutoConfiguration # Auto-discovers all channels + event listeners
β βββ MetricsEventListener # Micrometer metrics via EventBus
β βββ TracingNotificationListener # OpenTelemetry tracing (Observation API)
β βββ NotifyHubHealthIndicator # Actuator health (+ circuit breaker status)
β βββ NotifyHubInfoContributor # Actuator info endpoint
β βββ SpringEventNotificationListener # Spring ApplicationEvents
β βββ NotifyProperties # application.yml binding
β
βββ notify-admin/ # Admin dashboard (Thymeleaf)
β βββ NotifyAdminController # /notify-admin/*
β
βββ notify-mcp/ # MCP Server for AI agents
β βββ NotifyMcpServer # Spring Boot headless main
β βββ McpServerRunner # STDIO MCP server bootstrap
β βββ tools/ # 36 MCP tools (send, batch, audiences, DLQ, analytics)
β
βββ notify-queue-rabbitmq/ # RabbitMQ integration
βββ notify-queue-kafka/ # Kafka integration
β
βββ notify-demo/ # Demo app (run it!)
Resilience Pipeline
Every notification passes through a handler chain before hitting the channel:
Deduplication β Rate Limit β Circuit Breaker β Template Rendering β Retry + Send β DLQ
Each handler can short-circuit (e.g., dedup skips duplicates, circuit breaker rejects when open). The pipeline is fully optional β handlers are skipped when their dependency is null.
Design Principles
notify-core has zero Spring dependency β use it in any Java project
Channels are pluggable β implement NotificationChannel, register as a Spring bean
Slack, Telegram, Discord, Teams, WebSocket, Google Chat use zero external SDKs β only JDK java.net.http.HttpClient
Template engine is replaceable β implement TemplateEngine interface
Spring Boot starter auto-configures everything β just add the dependency
Async support β sendAsync() and sendAllAsync() with CompletableFuture
Conditional auto-config β channel beans only load when their module is on classpath
Unified event system β NotificationEventBus replaces scattered listener calls, backward-compatible via LegacyListenerAdapter
Maven Central
NotifyHub is published on Maven Central . No extra repositories needed.
Search on Maven Central: io.github.gabrielbbaldez
Available Modules
Below is every module, what it does, when you need it, and how to add it.
notify-spring-boot-starter β The Main Dependency
What it does: Auto-configures NotifyHub inside a Spring Boot application. Automatically discovers channel beans, wires retry policies, tracking, rate limiting, DLQ, Micrometer metrics, OpenTelemetry tracing, Actuator health checks, and Spring events. Includes notify-core and notify-email transitively.
When to use: You're building a Spring Boot app and want automatic setup. This is the only required dependency for most projects.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-spring-boot-starter</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-core β Core API (No Spring)
What it does: Contains the entire fluent API (NotifyHub, NotificationBuilder, Channel, Notification, Priority, Attachment, RetryPolicy), plus interfaces for channels, templates, tracking, DLQ, rate limiting, and routing. Has zero Spring dependency β uses only SLF4J and Mustache.
When to use: You want to use NotifyHub in a plain Java project without Spring Boot, or you're building a library/framework on top of it.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-core</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-email β SMTP Email Channel
What it does: Sends emails via any SMTP server (Gmail, Outlook, Amazon SES, Mailtrap, etc). Supports HTML and plain text, file attachments, TLS/SSL, and custom sender name. Uses Jakarta Mail internally.
When to use: You want to send email notifications. Already included by notify-spring-boot-starter.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-email</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-sms β Twilio SMS + WhatsApp Channel
What it does: Sends SMS and WhatsApp messages through the Twilio API. Handles phone number formatting (E.164) and the whatsapp: prefix automatically.
When to use: You need to send SMS or WhatsApp messages. Requires a Twilio account with Account SID, Auth Token, and a phone number.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-sms</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-slack β Slack Webhook Channel
What it does: Sends messages to a Slack channel via Incoming Webhooks. Uses the JDK HttpClient β no external SDK needed.
When to use: You want to post notifications to Slack. Requires a Slack Incoming Webhook URL (created at api.slack.com/apps ).
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-slack</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-telegram β Telegram Bot Channel
What it does: Sends messages to Telegram chats/groups/channels via the Bot API. Supports a default chat ID and per-notification targeting. Uses the JDK HttpClient.
When to use: You want to send Telegram messages. Requires a bot token from @BotFather .
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-telegram</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-discord β Discord Webhook Channel
What it does: Sends messages to a Discord channel via Webhooks. Supports custom bot username and avatar. Uses the JDK HttpClient.
When to use: You want to post notifications to Discord. Requires a Discord webhook URL (channel Settings > Integrations > Webhooks).
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-discord</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-teams β Microsoft Teams Channel
What it does: Sends MessageCard notifications to a Teams channel via Incoming Webhooks. Uses the JDK HttpClient.
When to use: You want to post notifications to Microsoft Teams. Requires a Teams Incoming Webhook URL (channel > Connectors > Incoming Webhook).
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-teams</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-push-firebase β Firebase Cloud Messaging (FCM)
What it does: Sends push notifications to mobile devices (Android/iOS) and web apps via Firebase Cloud Messaging. Uses the Firebase Admin SDK with service account credentials.
When to use: You want to send push notifications to mobile apps. Requires a Firebase project with a service account JSON credentials file.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-push-firebase</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-webhook β Generic Webhook Channel
What it does: Sends notifications to any HTTP endpoint (REST APIs, PagerDuty, Datadog, custom services). Supports configurable payload templates with {{recipient}}, {{subject}}, {{content}} placeholders, custom headers, PUT/POST methods, and timeouts.
When to use: You want to integrate with any external service that has an HTTP API, or create custom webhook integrations.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-webhook</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-websocket β WebSocket Channel
What it does: Sends notifications over WebSocket connections using the JDK java.net.http.WebSocket API. Supports configurable message format with {{recipient}}, {{subject}}, {{content}} placeholders, custom headers, auto-reconnect with backoff, and connection timeout. Zero external dependencies.
When to use: You want to send real-time notifications over WebSocket to a server (e.g., live dashboards, chat systems, or custom WebSocket consumers).
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-websocket</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-google-chat β Google Chat Channel
What it does: Sends messages to Google Chat spaces via Incoming Webhooks. Posts JSON payloads using the JDK HttpClient β no external SDK needed.
When to use: You want to send notifications to a Google Chat space. Requires a Google Chat webhook URL (space Settings > Apps & integrations > Webhooks).
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-google-chat</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-twitch β Twitch Channel
What it does: Sends chat messages and polls to Twitch channels via the Helix API. Uses the JDK HttpClient β no external SDK needed.
When to use: You want to send notifications to Twitch chat or create polls. Requires Twitch API OAuth 2.0 credentials (Client ID + OAuth token).
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-twitch</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-youtube β YouTube Channel
What it does: Sends live chat messages to YouTube live streams via the YouTube Data API v3. Uses the JDK HttpClient β no external SDK needed.
When to use: You want to send notifications to YouTube live chat. Requires a Google API key or OAuth 2.0 credentials with YouTube Data API v3 enabled.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-youtube</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-instagram β Instagram Channel
What it does: Sends DMs and feed posts via the Meta Graph API (Instagram Graph API). Uses the JDK HttpClient β no external SDK needed.
When to use: You want to send notifications via Instagram. Requires a Facebook Developer account with Instagram Graph API access.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-instagram</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-sendgrid β SendGrid Email Channel
What it does: Sends transactional emails via SendGrid API with built-in delivery event tracking (delivered, opened, clicked, bounced). Uses the JDK HttpClient β no external SDK needed.
When to use: You want email delivery tracking beyond basic SMTP, or you already use SendGrid as your email provider.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-sendgrid</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-tiktok-shop β TikTok Shop Channel
What it does: Sends messages to TikTok Shop sellers via the TikTok Shop API. Handles HMAC-SHA256 request signing automatically. Uses the JDK HttpClient.
When to use: You want to send notifications to TikTok Shop sellers (order updates, customer messages).
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-tiktok-shop</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-facebook β Facebook Channel
What it does: Sends Facebook Page posts and Messenger messages via the Graph API. Uses the JDK HttpClient β no external SDK needed.
When to use: You want to post to Facebook Pages or send Messenger messages.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-facebook</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-whatsapp β WhatsApp Cloud API Channel
What it does: Sends WhatsApp messages directly via Meta's Cloud API (no Twilio needed). Uses the JDK HttpClient β zero external dependencies.
When to use: You want to send WhatsApp messages using Meta's official Cloud API instead of Twilio.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-whatsapp</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-aws-sns β AWS SNS Channel
What it does: Publishes messages to AWS SNS topics or sends direct SMS via AWS Simple Notification Service. Uses AWS SDK v2.
When to use: You're already in the AWS ecosystem and want to use SNS for notifications.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-aws-sns</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-mailgun β Mailgun Channel
What it does: Sends transactional emails via Mailgun REST API. Uses the JDK HttpClient β no external SDK needed.
When to use: You use Mailgun as your email provider.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-mailgun</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-pagerduty β PagerDuty Channel
What it does: Creates PagerDuty incidents via the Events API v2. Uses the JDK HttpClient β no external SDK needed.
When to use: You want to trigger PagerDuty alerts/incidents from your notification pipeline.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-pagerduty</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-kick β Kick Channel
What it does: Sends chat messages to Kick channels via the Kick Public API. Supports bot and user message types with OAuth 2.1 authentication and token refresh.
When to use: You want to send chat messages to Kick streaming channels from your notification pipeline.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-kick</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-tracker-jpa β JPA Delivery Tracker
What it does: Persists delivery receipts to a relational database (MySQL, PostgreSQL, H2, etc.) using Spring Data JPA. Stores notification ID, channel, recipient, status, timestamp, and error messages. Provides query methods for filtering and counting.
When to use: You want delivery tracking data to survive restarts (instead of the default in-memory tracker). Requires Spring Data JPA and a database on the classpath.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-tracker-jpa</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-admin β Admin Dashboard
What it does: Provides a built-in web UI at /notify-admin with 4 pages: Dashboard (overview metrics), Tracking (delivery receipts), DLQ (failed notifications), and Channels (status). Built with Thymeleaf, dark theme, fully responsive.
When to use: You want a visual admin panel to monitor your notification system without building one from scratch. Requires notify.admin.enabled=true in your config.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-admin</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-queue-rabbitmq β RabbitMQ Message Queue
What it does: Adds async notification processing via RabbitMQ. Includes a producer (enqueue notifications), a consumer (reads from queue and sends via NotifyHub), and Spring Boot auto-configuration with exchange/queue/binding setup.
When to use: You need to decouple notification sending from your main application flow, or you're in a microservice architecture where one service enqueues and another sends.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-queue-rabbitmq</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-queue-kafka β Apache Kafka Message Queue
What it does: Same as RabbitMQ module but uses Apache Kafka as the message broker. Sends notifications to a Kafka topic with channel:recipient as the message key for partition ordering.
When to use: You're already using Kafka in your infrastructure, or you need high-throughput notification processing at scale.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-queue-kafka</artifactId>
<version>1.0 .0 </version>
</dependency>
notify-mcp β MCP Server for AI Agents
What it does: Exposes all NotifyHub channels as MCP (Model Context Protocol) tools, allowing AI agents (Claude Desktop, Claude Code, Cursor) to send notifications through natural language commands. Runs as a headless Spring Boot app communicating via STDIO JSON-RPC. Provides 27 tools: send via any channel, batch send, audience management, DLQ monitoring, and delivery analytics.
When to use: You want AI agents to send notifications on your behalf. Configure the JAR path in your MCP client's config file and the agent will discover all available tools automatically.
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-mcp</artifactId>
<version>1.0 .0 </version>
</dependency>
What Do I Need?
I want to... Add these dependencies Send emails from Spring Boot notify-spring-boot-starter (already includes email)Send SMS or WhatsApp notify-spring-boot-starter + notify-smsSend to Slack notify-spring-boot-starter + notify-slackSend to Telegram notify-spring-boot-starter + notify-telegramSend to Discord notify-spring-boot-starter + notify-discordSend to Microsoft Teams notify-spring-boot-starter + notify-teamsSend mobile push (FCM) notify-spring-boot-starter + notify-push-firebaseSend to any HTTP API notify-spring-boot-starter + notify-webhookSend via WebSocket notify-spring-boot-starter + notify-websocketSend to Google Chat notify-spring-boot-starter + notify-google-chatSend to Twitch chat notify-spring-boot-starter + notify-twitchSend to YouTube live chat notify-spring-boot-starter + notify-youtubeSend to Instagram notify-spring-boot-starter + notify-instagramSend via SendGrid (with tracking) notify-spring-boot-starter + notify-sendgridSend to TikTok Shop notify-spring-boot-starter + notify-tiktok-shopSend to Facebook Page/Messenger notify-spring-boot-starter + notify-facebookSend WhatsApp via Cloud API notify-spring-boot-starter + notify-whatsappSend via AWS SNS notify-spring-boot-starter + notify-aws-snsSend via Mailgun notify-spring-boot-starter + notify-mailgunCreate PagerDuty incidents notify-spring-boot-starter + notify-pagerdutySend Kick chat messages notify-spring-boot-starter + notify-kickPrevent duplicate sends notify-spring-boot-starter (built-in, config-driven)A/B test templates notify-spring-boot-starter (built-in, use .templateVersion())Persist tracking to database notify-spring-boot-starter + notify-tracker-jpaAdmin dashboard UI notify-spring-boot-starter + notify-adminUse without Spring Boot notify-core + channel modules you needAsync processing via RabbitMQ notify-spring-boot-starter + notify-queue-rabbitmqAsync processing via Kafka notify-spring-boot-starter + notify-queue-kafkaLet AI agents send notifications notify-mcp (standalone JAR)Everything at once notify-spring-boot-starter + all channel modules above
Roadmap
v0.1.0 β Core API, Email, SMS, WhatsApp, Mustache templates, Spring Boot starter
v0.2.0 β Slack, Telegram, Discord, async sending, scheduling, delivery tracking
v0.3.0 β Teams, Firebase Push, Webhook, attachments, priority, rate limiting, DLQ, i18n, batch send, JPA tracker, Micrometer metrics, Actuator health, Spring events, conditional routing, admin dashboard (80+ tests)
v0.4.0 β WebSocket channel, Google Chat channel, message deduplication, template versioning (105+ tests, 16 modules)
v0.5.0 β MCP Server module: 13 AI agent tools for sending notifications via Claude Desktop, Claude Code, Cursor (130+ tests, 17 modules)
v0.6.0 β Named recipients, Docker images (MCP + REST API), Swagger UI, RabbitMQ + Kafka message queue modules, GitHub Pages landing page
v0.7.0 β Twitter/X, LinkedIn, Notion channels (14 channels, 16 MCP tools, 22 modules)
v0.8.0 β Twitch, YouTube channels, admin dashboard redesign (16 channels, 18 MCP tools, 24 modules)
v0.9.0 β MCP advanced tools: audiences, contacts, batch send, DLQ, analytics (16 channels, 26 MCP tools)
v0.10.0 β Instagram channel: DMs and feed posts via Meta Graph API (17 channels, 27 MCP tools, 25 modules)
v1.0.0 β SendGrid, TikTok Shop, Facebook, WhatsApp Cloud API, AWS SNS, Mailgun, PagerDuty channels, scheduled notifications MCP tools, JaCoCo coverage (25 channels, 37 MCP tools, 28 modules)
v1.1.0 β Architecture improvements: resilience pipeline (circuit breaker, bulkhead, handler chain), unified event system (NotificationEventBus, EventType, MetricsEventListener), god object refactoring (NotificationExecutor, NotificationScheduler extracted from NotifyHub), multi-channel orchestration, built-in A/B testing, cron scheduling, quiet hours, TestNotifyHub test utility, channel template module, Levenshtein error suggestions, enhanced health indicator with circuit breaker status
Requirements
Java 17+
Spring Boot 3.x (for starter β optional, core works standalone)
Maven 3.8+ (for building)
License
MIT License β see LICENSE for details.
Built by Gabriel Baldez