CCM
/MCP
SkillsMCPMarketplacesDigestToolsAdvertise

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by Category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP Servers by Category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by Category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control

Claude Code Marketplaces

Discover Claude Code plugins, extensions, and tools. Automatically updated directory of Anthropic Claude AI marketplaces with development tools, productivity plugins, and integrations.

Resources

  • Browse Skills
  • Browse MCP Servers
  • Browse Marketplaces
  • Skill index
  • MCP index
  • Marketplace index
  • Plugins Reference

Community

  • About
  • Tools
  • Feedback
  • Privacy Policy
  • Advertise

Built for the Claude Code community with Claude Code by mertbuilds.com

Independent project, not affiliated with Anthropic
gabrielbbaldez avatar

NotifyHub — Unified Notification MCP Server

gabrielbbaldez/notify-hub
authSTDIOregistry active
Summary

If you're tired of wiring up email, SMS, Slack, Discord, Teams, Telegram, WhatsApp, and two dozen other notification channels separately, this gives Claude a unified send operation across all of them. The MCP tools expose NotifyHub's Java notification library: send to single or multiple recipients, chain fallbacks (email fails, try SMS), schedule messages, attach files, apply templates with i18n, and track delivery status. You get 36 tools covering everything from basic sends to batch operations, A/B testing, rate limiting, and dead letter queue management. Reach for this when you want Claude to orchestrate notifications without writing channel-specific code for each service or dealing with retry logic, circuit breakers, and deduplication yourself.

CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
ego lite browserego lite browser
ego lite browser
Fastest browser for AI agents to run web automation tasks, always free.
Download Free life-time →
belt - the only tool your agent needs
belt - the only tool your agent needs
belt cli automatically finds the best tools and skills for your agent. image, video, music, tts...
one prompt install →
Open Steps
Open Steps
Free an open-source skills that make AI coding agents easier to understand, verify, and control.
Download for free →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Agent, connect blockchain
Agent, connect blockchain
Connect your Claude agent to live crypto prices and trading routes via 1inch
Get the MCP →
Granola, the best AI meeting recorder
Granola, the best AI meeting recorder
Notes, actions and memory. Without a meeting bot. First month 100% off.
Download for free →
CodeHealth MCP ServerCodeHealth MCP Server
CodeHealth MCP Server
Protect your code quality, stop the AI slop.
Try For Free →
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
ego lite browserego lite browser
ego lite browser
Fastest browser for AI agents to run web automation tasks, always free.
Download Free life-time →
belt - the only tool your agent needs
belt - the only tool your agent needs
belt cli automatically finds the best tools and skills for your agent. image, video, music, tts...
one prompt install →
Open Steps
Open Steps
Free an open-source skills that make AI coding agents easier to understand, verify, and control.
Download for free →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Agent, connect blockchain
Agent, connect blockchain
Connect your Claude agent to live crypto prices and trading routes via 1inch
Get the MCP →
Granola, the best AI meeting recorder
Granola, the best AI meeting recorder
Notes, actions and memory. Without a meeting bot. First month 100% off.
Download for free →
CodeHealth MCP ServerCodeHealth MCP Server
CodeHealth MCP Server
Protect your code quality, stop the AI slop.
Try For Free →

NotifyHub

NotifyHub

One API. Every channel.
Unified notification library for Java and Spring Boot.

Java 17+ Spring Boot 3.x Maven Central License: MIT CI codecov

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?

ProblemWithout NotifyHubWith NotifyHub
gmailEmailJavaMail config, MIME types, Session....via(EMAIL)
📱SMSTwilio SDK, different API entirely.via(SMS)
whatsappWhatsAppAnother Twilio setup, prefix logic.via(WHATSAPP)
slackSlackWebhook HTTP, JSON payload.via(SLACK)
telegramTelegramBot API, HTTP client setup.via(TELEGRAM)
discordDiscordWebhook HTTP, JSON payload.via(DISCORD)
👥TeamsIncoming Webhook, MessageCard JSON.via(TEAMS)
firebasePushFirebase Admin SDK, credentials....via(PUSH)
🔗WebhookCustom HTTP, payload template.via(Channel.custom("pagerduty"))
socketdotioWebSocketJava WebSocket API, reconnect logic.via(WEBSOCKET)
googlechatGoogle ChatWebhook HTTP, JSON payload.via(GOOGLE_CHAT)
whiteTwitter/XOAuth 1.0a, API v2 setup.via(TWITTER)
💼LinkedInOAuth 2.0, REST API setup.via(LINKEDIN)
whiteNotionIntegration Token, API setup.via(NOTION)
twitchTwitchOAuth 2.0, Twitch API setup.via(TWITCH)
youtubeYouTubeYouTube Data API v3 setup.via(YOUTUBE)
instagramInstagramMeta Graph API setup.via(INSTAGRAM)
📧SendGridSendGrid API, webhook tracking.via(Channel.custom("sendgrid"))
EE1D52TikTok ShopHMAC-SHA256, Shop API.via(TIKTOK_SHOP)
facebookFacebookGraph API, Page tokens.via(FACEBOOK)
☁️AWS SNSAWS SDK, credentials, ARN.via(Channel.custom("aws-sns"))
F06B66MailgunMailgun API, domain setup.via(Channel.custom("mailgun"))
06AC38PagerDutyEvents API v2, routing key.via(Channel.custom("pagerduty"))
kickKickPublic API, OAuth 2.1.via(KICK)
Multiple channelsCompletely different code for eachSame fluent API
FallbackManual try/catch chain.fallback(SMS)
RetryImplement yourselfBuilt-in exponential backoff
AsyncThread pools, CompletableFuture.sendAsync()
SchedulingScheduledExecutor, timer logic.schedule(Duration.ofMinutes(30))
TemplatesEach channel has its own engineOne template, all channels
i18nManual locale resolution.locale(Locale.PT_BR)
Rate limitingToken bucket from scratchConfig-driven per-channel
TrackingBuild your own delivery logBuilt-in receipts + JPA
Dead lettersLost in the voidAuto-captured in DLQ
DeduplicationTrack sent messages yourselfBuilt-in content hash / explicit key
Template versionsManage files manually.templateVersion("v2") + A/B test
BatchLoop and pray.toAll(users).send()
MonitoringWire Micrometer yourselfAuto-configured counters
Health checksWrite an Actuator indicatorAuto-configured
Admin UIBuild your own dashboardBuilt-in /notify-admin
Circuit breakerImplement yourself per channelBuilt-in per-channel circuit breaker
OrchestrationManual escalation logic.orchestrate().first(EMAIL).ifNoOpen(24h).then(PUSH)
A/B testingExternal service + glue codeBuilt-in .abTest("exp").variant(...).split(50,50)
TestingMock everythingTestNotifyHub captures all sends
New channelBuild from scratchImplement one interface

Table of Contents

  • Quick Start
  • Features
    • Fallback Chain
    • Multi-Channel Send
    • Async Sending
    • Retry with Backoff
    • Templates (Mustache)
    • i18n (Internationalization)
    • Attachments
    • Priority Levels
    • Rate Limiting
    • Dead Letter Queue (DLQ)
    • Batch Send
    • Delivery Tracking
    • Scheduled Notifications
    • Notification Routing
    • Notifiable Interface
    • Message Deduplication
    • Template Versioning
    • Custom Channels
    • Event Listeners + Spring Events
    • Named Recipients
    • Message Queue (RabbitMQ / Kafka)
    • Circuit Breaker
    • Bulkhead (Concurrency Isolation)
    • Multi-Channel Orchestration
    • A/B Testing
    • Cron Scheduling
    • Quiet Hours
    • Testing Utilities
  • Supported Channels
  • Admin Dashboard
  • Spring Boot Integration
  • Configuration Reference
  • Without Spring Boot
  • MCP Server (AI Agents)
  • Running the Demo
  • Architecture
  • Maven Central
  • Roadmap
  • License

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

ChannelProviderModule
gmailEmailSMTP (Gmail, SES, Outlook, any)notify-email
📱SMSTwilionotify-sms
whatsappWhatsAppTwilionotify-sms
slackSlackIncoming Webhooksnotify-slack
telegramTelegramBot APInotify-telegram
discordDiscordWebhooksnotify-discord
👥Microsoft TeamsIncoming Webhooksnotify-teams
firebasePush (FCM)Firebase Cloud Messagingnotify-push-firebase
🔗WebhookAny HTTP endpointnotify-webhook
socketdotioWebSocketJDK WebSocket (java.net.http)notify-websocket
googlechatGoogle ChatWebhooksnotify-google-chat
whiteTwitter/XAPI v2 (OAuth 1.0a)notify-twitter
💼LinkedInREST API (OAuth 2.0)notify-linkedin
whiteNotionAPI (Integration Token)notify-notion
twitchTwitchHelix API (OAuth 2.0 auto-refresh)notify-twitch
youtubeYouTubeData API v3 (OAuth auto-refresh)notify-youtube
instagramInstagramMeta Graph APInotify-instagram
📧SendGridSendGrid API (delivery tracking)notify-sendgrid
EE1D52TikTok ShopTikTok Shop API (HMAC-SHA256)notify-tiktok-shop
facebookFacebookGraph API (Page + Messenger)notify-facebook
whatsappWhatsApp CloudMeta Cloud API (direct, no Twilio)notify-whatsapp
☁️AWS SNSAWS SDK v2notify-aws-sns
F06B66MailgunMailgun REST APInotify-mailgun
06AC38PagerDutyEvents API v2notify-pagerduty
kickKickPublic APInotify-kick
➕CustomAny — implement one interfacenotify-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

GET /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

ToolDescriptionRequired Params
send_notificationSend via any channel (generic)channel, recipient, body or template
send_emailSend emailto, body or template
send_smsSend SMS via Twiliophone, body or template
send_slackSend to Slack channelrecipient, body or template
send_telegramSend via Telegram Botrecipient, body or template
send_discordSend to Discord channelrecipient, body or template
send_whatsappSend WhatsApp via Twiliophone, body or template
send_teamsSend to Microsoft Teamsrecipient, body or template
send_google_chatSend to Google Chatrecipient, body or template
send_pushSend push via Firebasepush_token, body
send_twitterPost a tweet on Twitter/Xbody or template
send_linkedinPublish a post on LinkedInbody or template
send_notionCreate a page in Notionrecipient, body or template
send_twitchSend Twitch chat message + pollsrecipient, body or template
send_youtubeSend YouTube live chat messagerecipient, body or template
send_instagramSend Instagram DM or feed postrecipient, body or template
send_multi_channelSend to multiple channelschannels[], recipient, body or template
send_batchSend to multiple recipients at oncerecipients[], channel, body or template
send_to_audienceSend to a named audienceaudience, channel, body or template
list_channelsList configured channels(none)
list_delivery_receiptsQuery delivery history(none)
list_dead_lettersView failed notifications (DLQ)(none)
create_contactCreate a contact with tagsname
list_contactsList contacts (filter by tag)(none)
create_audienceCreate audience with tag filtersname, tags[]
list_audiencesList audiences with contact counts(none)
get_analyticsDelivery stats by channel/status(none)
send_tiktok_shopSend TikTok Shop notificationrecipient, body or template
send_facebookSend Facebook page post or Messenger DMrecipient, body or template
check_email_statusCheck SendGrid email delivery statusmessage_id
schedule_notificationSchedule a notification for later deliverychannel, recipient, body, send_at
list_scheduled_notificationsList all scheduled notifications(none)
cancel_scheduled_notificationCancel a pending scheduled notificationnotification_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:

ChannelVariableRequiredExample
EmailNOTIFY_CHANNELS_EMAIL_HOSTNo (default: smtp.gmail.com)smtp.gmail.com
NOTIFY_CHANNELS_EMAIL_PORTNo (default: 587)587
NOTIFY_CHANNELS_EMAIL_USERNAMEYes (for email)you@gmail.com
NOTIFY_CHANNELS_EMAIL_PASSWORDYes (for email)abcd efgh ijkl mnop (App Password)
NOTIFY_CHANNELS_EMAIL_FROMYes (for email)you@gmail.com
DiscordNOTIFY_CHANNELS_DISCORD_WEBHOOK_URLYes (for discord)https://discord.com/api/webhooks/...
NOTIFY_CHANNELS_DISCORD_USERNAMENoNotifyHub
NOTIFY_CHANNELS_DISCORD_AVATAR_URLNohttps://example.com/avatar.png
NOTIFY_CHANNELS_DISCORD_RECIPIENTS_<NAME>NoNamed alias webhook URL
SlackNOTIFY_CHANNELS_SLACK_WEBHOOK_URLYes (for slack)https://hooks.slack.com/services/...
NOTIFY_CHANNELS_SLACK_RECIPIENTS_<NAME>NoNamed alias webhook URL
TelegramNOTIFY_CHANNELS_TELEGRAM_BOT_TOKENYes (for telegram)123456:ABC-DEF...
NOTIFY_CHANNELS_TELEGRAM_CHAT_IDYes (for telegram)123456789
NOTIFY_CHANNELS_TELEGRAM_RECIPIENTS_<NAME>NoNamed alias chat ID
Google ChatNOTIFY_CHANNELS_GOOGLE_CHAT_WEBHOOK_URLYes (for gchat)https://chat.googleapis.com/v1/spaces/...
NOTIFY_CHANNELS_GOOGLE_CHAT_RECIPIENTS_<NAME>NoNamed alias webhook URL
TeamsNOTIFY_CHANNELS_TEAMS_WEBHOOK_URLYes (for teams)https://outlook.office.com/webhook/...
NOTIFY_CHANNELS_TEAMS_RECIPIENTS_<NAME>NoNamed alias webhook URL
SMSNOTIFY_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)+12025551234
WhatsAppNOTIFY_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:

  • http://localhost:8080 — API endpoints
  • http://localhost:8080/notify-admin — Admin dashboard

Demo Endpoints

MethodEndpointDescription
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


View the full README on GitHub

Featured
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
ego lite browserego lite browser
ego lite browser
Fastest browser for AI agents to run web automation tasks, always free.
Download Free life-time →
belt - the only tool your agent needs
belt - the only tool your agent needs
belt cli automatically finds the best tools and skills for your agent. image, video, music, tts...
one prompt install →
Open Steps
Open Steps
Free an open-source skills that make AI coding agents easier to understand, verify, and control.
Download for free →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Agent, connect blockchain
Agent, connect blockchain
Connect your Claude agent to live crypto prices and trading routes via 1inch
Get the MCP →
Granola, the best AI meeting recorder
Granola, the best AI meeting recorder
Notes, actions and memory. Without a meeting bot. First month 100% off.
Download for free →
CodeHealth MCP ServerCodeHealth MCP Server
CodeHealth MCP Server
Protect your code quality, stop the AI slop.
Try For Free →

Configuration

NOTIFY_CHANNELS_EMAIL_HOST

SMTP server host for email notifications

NOTIFY_CHANNELS_EMAIL_PORT

SMTP server port

NOTIFY_CHANNELS_EMAIL_USERNAMEsecret

SMTP authentication username

NOTIFY_CHANNELS_EMAIL_PASSWORDsecret

SMTP authentication password

NOTIFY_CHANNELS_EMAIL_FROM

Default sender email address

NOTIFY_CHANNELS_SMS_ACCOUNTSIDsecret

Twilio Account SID for SMS

NOTIFY_CHANNELS_SMS_AUTHTOKENsecret

Twilio Auth Token for SMS

NOTIFY_CHANNELS_SMS_FROMNUMBER

Twilio sender phone number

NOTIFY_CHANNELS_SLACK_WEBHOOKURLsecret

Slack incoming webhook URL

NOTIFY_CHANNELS_TELEGRAM_BOTTOKENsecret

Telegram Bot API token

NOTIFY_CHANNELS_TELEGRAM_CHATID

Telegram chat/group ID

NOTIFY_CHANNELS_DISCORD_WEBHOOKURLsecret

Discord webhook URL

NOTIFY_CHANNELS_TEAMS_WEBHOOKURLsecret

Microsoft Teams webhook URL

NOTIFY_CHANNELS_GOOGLECHAT_WEBHOOKURLsecret

Google Chat webhook URL

NOTIFY_CHANNELS_TWITTER_APIKEYsecret

Twitter/X API key

NOTIFY_CHANNELS_TWITTER_APISECRETsecret

Twitter/X API secret

NOTIFY_CHANNELS_TWITTER_ACCESSTOKENsecret

Twitter/X access token

NOTIFY_CHANNELS_TWITTER_ACCESSTOKENSECRETsecret

Twitter/X access token secret

NOTIFY_CHANNELS_LINKEDIN_ACCESSTOKENsecret

LinkedIn OAuth2 access token

NOTIFY_CHANNELS_LINKEDIN_ORGANIZATIONID

LinkedIn organization/company ID

NOTIFY_CHANNELS_WHATSAPP_ACCOUNTSIDsecret

Twilio Account SID for WhatsApp

NOTIFY_CHANNELS_WHATSAPP_AUTHTOKENsecret

Twilio Auth Token for WhatsApp

NOTIFY_CHANNELS_WHATSAPP_FROMNUMBER

WhatsApp sender number (with whatsapp: prefix)

NOTIFY_CHANNELS_PUSH_FIREBASECREDENTIALSsecret

Firebase Cloud Messaging credentials JSON (base64 or path)

Registryactive
Packageghcr.io/gabrielbbaldez/notify-hub:1.1.0
TransportSTDIO
AuthRequired
UpdatedMar 17, 2026
View on GitHub