Getting Started
Choose your path:
Use NotifyHub with Claude, Cursor, or any AI assistant
No programming required. Just download, setup, and start talking to your AI.
Step 1: Download
Download the NotifyHub MCP executable for your platform:
# Windows
https://github.com/GabrielBBaldez/notify-hub/releases/latest
# Or if you have Java installed:
java -jar notifyhub-mcp.jar --setup
Step 2: Run the Setup Wizard
The wizard guides you through everything — no config files to edit manually.
notifyhub-mcp --setup
# The wizard will:
# 1. Ask for your license key (optional)
# 2. Let you choose channels (Email, Slack, Telegram, etc.)
# 3. Ask for credentials (API keys, tokens)
# 4. Detect your AI editor (Claude, Cursor, VS Code...)
# 5. Generate the config automatically
Step 3: Use with your AI
Open Claude Desktop, Cursor, or any supported editor and just talk:
"Send an email to john@example.com saying the project is complete"
"Notify the team on Slack #general that deployment succeeded"
"Send a Telegram message to the ops group about the outage"
"Post a tweet announcing our v2.0 release"
"Send an SMS to +1234567890 with the login code 482901"
Updating
Check for updates and install with one command:
notifyhub-mcp --update
Updates only replace the executable — your channel configuration stays intact.
Integrate NotifyHub into your Java / Spring Boot application
Add the dependency, configure your channels, and send notifications with a fluent API.
Step 1: Add the dependency
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>io.github.gabrielbbaldez</groupId>
<artifactId>notify-email</artifactId>
</dependency>
Step 2: Configure
notify:
channels:
email:
host: smtp.gmail.com
port: 587
username: ${GMAIL_USER}
password: ${GMAIL_PASS}
from: noreply@myapp.com
tls: true
Step 3: Send
@Service
public class OrderService {
private final NotifyHub notify;
public OrderService(NotifyHub notify) {
this.notify = notify;
}
public void confirmOrder(User user) {
notify.to(user)
.via(Channel.EMAIL)
.fallback(Channel.SMS)
.subject("Order Confirmed!")
.content("Your order has shipped!")
.send();
}
}
notify-slack but don't set notify.channels.slack.webhook-url, the Slack channel won't be registered.Quick Start
Three steps to send your first notification:
1. Add the dependency
Add notify-spring-boot-starter and at least one channel module (e.g., notify-email) to your project.
2. Configure the channel
notify:
channels:
email:
host: smtp.gmail.com
port: 587
username: ${GMAIL_USER}
password: ${GMAIL_PASS}
from: noreply@myapp.com
from-name: MyApp
tls: true
3. Inject and send
@Service
public class OrderService {
private final NotifyHub notify;
public OrderService(NotifyHub notify) {
this.notify = notify;
}
public void confirmOrder(User user) {
notify.to(user)
.via(Channel.EMAIL)
.subject("Order Confirmed!")
.template("order-confirmed")
.param("orderId", "ORD-123")
.send();
}
}
notify-slack but don't set notify.channels.slack.webhook-url, the Slack channel won't be registered.Core Concepts
NotifyHub
The main entry point. In Spring Boot, it's auto-configured as a singleton bean. Standalone, use the builder:
NotifyHub hub = NotifyHub.builder()
.channel(emailChannel)
.channel(slackChannel)
.channels(List.of(smsChannel, pushChannel))
.templateEngine(new MustacheTemplateEngine())
.defaultRetryPolicy(RetryPolicy.exponential(3))
.listener(myListener)
.executor(Executors.newFixedThreadPool(4))
.scheduler(Executors.newScheduledThreadPool(2))
.tracker(new InMemoryNotificationTracker())
.rateLimiter(new TokenBucketRateLimiter(config))
.deadLetterQueue(new InMemoryDeadLetterQueue())
.router(myRouter)
.deduplicationStore(new InMemoryDeduplicationStore())
.auditLog(new InMemoryAuditLog())
.audienceManager(new AudienceManager(contactRepo))
.build();
Channel Enum
All built-in channels are represented by the Channel enum:
Channel.EMAIL, Channel.SMS, Channel.WHATSAPP, Channel.PUSH
Channel.SLACK, Channel.TELEGRAM, Channel.DISCORD, Channel.TEAMS
Channel.WEBSOCKET, Channel.GOOGLE_CHAT
Channel.TWITTER, Channel.LINKEDIN, Channel.NOTION
Channel.TWITCH, Channel.YOUTUBE, Channel.INSTAGRAM
Channel.TIKTOK_SHOP, Channel.FACEBOOK
Channel.custom("sendgrid") // SendGrid email with tracking
Channel.custom("pagerduty") // Custom channels
Priority
| Priority | Description |
|---|---|
Priority.URGENT | Bypasses rate limiting. Use for critical alerts. |
Priority.HIGH | High priority notification. |
Priority.NORMAL | Default priority. |
Priority.LOW | Low priority, best-effort delivery. |
DeliveryReceipt
Returned by sendTracked() and sendAllTracked():
DeliveryReceipt receipt = notify.to(user)
.via(Channel.EMAIL)
.template("welcome")
.sendTracked();
receipt.getId(); // UUID
receipt.getChannelName(); // "email"
receipt.getRecipient(); // "user@example.com"
receipt.getStatus(); // PENDING, SCHEDULED, SENT, FAILED, CANCELLED
receipt.getTimestamp(); // Instant
receipt.getErrorMessage(); // null or error details
Fluent API
Entry Points
// Single recipient
notify.to(user) // Notifiable object
notify.to("user@example.com") // Email string
notify.toPhone("+5548999999999") // Phone number
// Batch recipients
notify.toAll(List.of("a@x.com", "b@x.com"))
notify.toAllNotifiable(List.of(user1, user2))
notify.toAudience("premium-users")
// Auto-route via preferred channels
notify.notify(user)
Builder Chain
The full fluent builder with all available methods:
notify.to(user)
.via(Channel.EMAIL) // Primary channel
.fallback(Channel.SMS) // Fallback if primary fails
.fallback(Channel.WHATSAPP) // Second fallback
.priority(Priority.HIGH) // URGENT, HIGH, NORMAL, LOW
.subject("Order confirmed") // Subject (email/push/teams)
.content("Your order #123 is ready") // Plain text body
.template("order-confirmed") // OR use Mustache template
.templateVersion("v2") // Template version (A/B testing)
.param("orderId", order.getId()) // Template parameter
.params(Map.of("k", "v")) // Bulk template params
.locale(Locale.PT_BR) // i18n locale
.attach(file) // File attachment (email)
.retry(3) // Override retry count
.deduplicationKey("order-123") // Prevent duplicates
.metadata("traceId", "abc-123") // Custom metadata
.send(); // Execute synchronously
Execution Methods
| Method | Description |
|---|---|
.send() | Synchronous. Uses fallback chain on failure. |
.sendAll() | Synchronous. Sends to ALL .via() channels. |
.sendAsync() | Asynchronous. Returns CompletableFuture<Void>. |
.sendAllAsync() | Async to all channels. |
.sendTracked() | Sync, returns DeliveryReceipt. |
.sendAllTracked() | Returns List<DeliveryReceipt>. |
.schedule(Duration) | Schedule for later (e.g., Duration.ofMinutes(30)). |
.scheduleAt(Instant) | Schedule at specific time. |
Examples
Fallback chain
notify.to(user)
.via(Channel.WHATSAPP).fallback(Channel.SMS).fallback(Channel.EMAIL)
.template("payment-reminder")
.param("amount", "R$ 150,00")
.send();
Multi-channel broadcast
notify.to(user)
.via(Channel.EMAIL).via(Channel.SLACK).via(Channel.TEAMS)
.subject("Security Alert")
.content("Login from new device detected")
.sendAll();
Tracked async
CompletableFuture<Void> future = notify.to(user)
.via(Channel.EMAIL)
.template("invoice")
.param("invoiceId", "INV-456")
.sendAsync();
future.thenRun(() -> log.info("Invoice email sent"));
Notifiable Interface
Implement this interface on your domain objects (User, Customer, etc.) to enable direct usage with notify.to(user):
public class User implements Notifiable {
public String getNotifyEmail() { return email; }
public String getNotifyPhone() { return phone; }
public String getNotifyPushToken() { return pushToken; }
public String getNotifyName() { return name; }
public List<Channel> getPreferredChannels() {
return List.of(Channel.EMAIL, Channel.PUSH);
}
}
All methods have default implementations returning null or empty lists. Override only the ones you need.
notify.notify(user), NotifyHub automatically sends via the user's preferred channels in order, using the remaining as fallbacks.Channels
NotifyHub supports 20 built-in channels. Each channel requires its own module dependency and configuration. Below is the complete reference for each channel.
notify.to(user)
.via(Channel.EMAIL, Channel.SLACK, Channel.PUSH)
.subject("Order Confirmed!")
.body("Your order #123 has been confirmed.")
.send();
.fallback(Channel.SMS) to automatically try another channel if the primary fails.Email (SMTP)
Send emails via any SMTP server (Gmail, AWS SES, Outlook, custom). Supports HTML templates, attachments, and TLS/SSL.
Module: notify-email
notify:
channels:
email:
host: smtp.gmail.com
port: 587
username: ${GMAIL_USER}
password: ${GMAIL_PASS}
from: noreply@myapp.com
from-name: MyApp
tls: true
SmtpConfig config = SmtpConfig.builder()
.host("smtp.gmail.com").port(587)
.username("user").password("pass")
.from("noreply@app.com").fromName("MyApp")
.tls(true)
.build();
Getting Credentials
- Gmail: Enable 2FA on your Google account, then go to App Passwords and generate one for "Mail"
- AWS SES: Go to SES Console > SMTP Settings > Create SMTP credentials
- Other SMTP: Use the host, port, username, and password provided by your email provider
Usage
notify.to("user@example.com")
.via(Channel.EMAIL)
.subject("Order Confirmed!")
.body("Your order #123 has been confirmed.")
.send();
SMS (Twilio)
Send SMS messages via the Twilio API.
Module: notify-sms
notify:
channels:
sms:
account-sid: ${TWILIO_ACCOUNT_SID}
auth-token: ${TWILIO_AUTH_TOKEN}
from-number: "+15551234567"
TwilioConfig config = TwilioConfig.builder()
.accountSid("AC...").authToken("token")
.fromNumber("+15551234567")
.build();
Getting Credentials
- Create an account at Twilio
- Go to Console > Account Info to find your Account SID and Auth Token
- Buy or use a trial phone number with SMS capability
Usage
notify.to("+5511999999999")
.via(Channel.SMS)
.body("Your verification code is 1234")
.send();
Send WhatsApp messages via Twilio or Meta WhatsApp Cloud API. Supports text messages and media attachments.
Module: notify-sms (same module as SMS)
Option A: Twilio Backend
notify:
channels:
whatsapp:
account-sid: ${TWILIO_ACCOUNT_SID}
auth-token: ${TWILIO_AUTH_TOKEN}
from-number: "+14155238886"
TwilioConfig config = TwilioConfig.builder()
.accountSid("AC...")
.authToken("token")
.fromNumber("+14155238886")
.build();
hub.registerChannel(new WhatsAppChannel(config));
Option B: Meta WhatsApp Cloud API
notify:
channels:
whatsapp:
access-token: ${WHATSAPP_ACCESS_TOKEN}
phone-number-id: ${WHATSAPP_PHONE_NUMBER_ID}
Getting Credentials
Option A — Twilio:
- Create an account at Twilio
- Go to Console > Account Info to find your Account SID and Auth Token
- In Messaging > Try it out > Send a WhatsApp message, activate the WhatsApp sandbox
- The From Number is the Twilio sandbox number (e.g.,
+14155238886)
Option B — Meta WhatsApp Cloud API:
- Go to Meta for Developers and create an app with "Conectar-se com clientes pelo WhatsApp"
- Create a Business Portfolio if prompted
- Go to WhatsApp > API Setup and click Generate Access Token
- Copy the Phone Number ID shown on the same page
- Add your phone number as a test recipient in step 3 and verify with the code
Usage
notify.to("+5511999999999")
.via(Channel.WHATSAPP)
.body("Your order has been shipped!")
.send();
Media Attachments
// Attach media (image, video, PDF) via public URL
notify.to("+5511999999999")
.via(Channel.WHATSAPP)
.body("Here is your invoice")
.param("mediaUrl", "https://example.com/invoice.pdf")
.send();
account-sid is set, Twilio is used. If access-token and phone-number-id are set, Meta Cloud API is used.Slack
Send messages to Slack channels or users via Incoming Webhooks.
Module: notify-slack
notify:
channels:
slack:
webhook-url: ${SLACK_WEBHOOK_URL}
recipients:
engineering: "https://hooks.slack.com/services/..."
alerts: "https://hooks.slack.com/services/..."
SlackConfig config = SlackConfig.builder()
.webhookUrl("https://hooks.slack.com/services/...")
.recipients(Map.of(
"engineering", "https://hooks.slack.com/services/...",
"alerts", "https://hooks.slack.com/services/..."
))
.build();
Getting Credentials
- Go to Slack API and create a new app (or select existing)
- Go to Incoming Webhooks and toggle it On
- Click Add New Webhook to Workspace and select the target channel
- Copy the Webhook URL (starts with
https://hooks.slack.com/services/...)
Usage
notify.to("engineering")
.via(Channel.SLACK)
.body("Deploy to production complete!")
.send();
recipients to map friendly names to webhook URLs, then send with notify.to("engineering").via(SLACK).send().Telegram
Send messages via the Telegram Bot API to any chat ID or channel.
Module: notify-telegram
notify:
channels:
telegram:
bot-token: ${TELEGRAM_BOT_TOKEN}
chat-id: ${TELEGRAM_CHAT_ID}
recipients:
admin: "123456789"
TelegramConfig config = TelegramConfig.builder()
.botToken("123456:ABC-DEF").chatId("987654321")
.recipients(Map.of("admin", "123456789"))
.build();
Getting Credentials
- Open Telegram and search for @BotFather
- Send
/newbot, choose a name and username to get the Bot Token - To get a Chat ID: add the bot to a group or send it a message, then call
https://api.telegram.org/bot{TOKEN}/getUpdatesand look for thechat.idfield
-1001234567890). For direct messages, it's a positive number.Usage
notify.to("admin")
.via(Channel.TELEGRAM)
.body("Alert: CPU usage above 90%")
.send();
Discord
Send messages via Discord webhooks with custom username and avatar.
Module: notify-discord
notify:
channels:
discord:
webhook-url: ${DISCORD_WEBHOOK_URL}
username: NotifyBot
avatar-url: "https://example.com/avatar.png"
recipients:
alerts: "https://discord.com/api/webhooks/..."
DiscordConfig config = DiscordConfig.builder()
.webhookUrl("https://discord.com/api/webhooks/...")
.username("NotifyBot").avatarUrl("https://...")
.recipients(Map.of("alerts", "https://discord.com/api/webhooks/..."))
.build();
Getting Credentials
- In Discord, go to the channel > Edit Channel > Integrations > Webhooks
- Click New Webhook, give it a name and avatar
- Click Copy Webhook URL
Usage
notify.to("alerts")
.via(Channel.DISCORD)
.body("New user signup: john@example.com")
.send();
Microsoft Teams
Send messages via Microsoft Teams Incoming Webhooks with MessageCard format.
Module: notify-teams
notify:
channels:
teams:
webhook-url: ${TEAMS_WEBHOOK_URL}
recipients:
devops: "https://outlook.office.com/webhook/..."
TeamsConfig config = TeamsConfig.builder()
.webhookUrl("https://outlook.office.com/webhook/...")
.recipients(Map.of("devops", "https://outlook.office.com/webhook/..."))
.build();
Getting Credentials
- In Microsoft Teams, go to the channel where you want notifications
- Click ... > Connectors > Incoming Webhook
- Give the webhook a name and optional image, then click Create
- Copy the Webhook URL
Usage
notify.to("devops")
.via(Channel.TEAMS)
.subject("Incident Alert")
.body("Database connection pool exhausted")
.send();
Google Chat
Send messages to Google Chat spaces via webhooks.
Module: notify-google-chat
notify:
channels:
google-chat:
webhook-url: ${GOOGLE_CHAT_WEBHOOK_URL}
recipients:
team: "https://chat.googleapis.com/..."
GoogleChatConfig config = GoogleChatConfig.builder()
.webhookUrl("https://chat.googleapis.com/v1/spaces/...")
.recipients(Map.of("team", "https://chat.googleapis.com/..."))
.build();
Getting Credentials
- Open the Google Chat space where you want to send messages
- Click the space name > Apps & integrations > Manage webhooks
- Click Add webhook, give it a name and (optional) avatar URL
- Copy the Webhook URL
Usage
notify.to("team")
.via(Channel.GOOGLE_CHAT)
.body("Sprint planning starts in 10 minutes")
.send();
Firebase Push
Send push notifications via Firebase Cloud Messaging (FCM).
Module: notify-push-firebase
notify:
channels:
push:
server-key: ${FIREBASE_SERVER_KEY}
FirebasePushConfig config = FirebasePushConfig.builder()
.serverKey("AAAA...")
.build();
Getting Credentials
- Go to Firebase Console and create or select a project
- Go to Project Settings > Cloud Messaging tab
- Copy the Server Key (Legacy API) or use the Firebase Admin SDK with a service account JSON
- Device tokens are generated by the client app (Android/iOS/Web) when registering for push notifications
Usage
notify.to("fcm-device-token-here")
.via(Channel.PUSH)
.subject("New Message")
.body("You have a new message from John")
.send();
WebSocket
Send real-time notifications via JDK WebSocket (java.net.http) with auto-reconnect.
Module: notify-websocket
notify:
channels:
websocket:
uri: wss://ws.example.com/notifications
reconnect-enabled: true
reconnect-delay-ms: 3000
max-reconnect-attempts: 10
headers:
Authorization: "Bearer ${WS_TOKEN}"
WebSocketConfig config = WebSocketConfig.builder()
.uri("wss://ws.example.com/notifications")
.reconnectEnabled(true).reconnectDelayMs(3000).maxReconnectAttempts(10)
.headers(Map.of("Authorization", "Bearer token"))
.messageFormat("json")
.build();
Usage
notify.to("session-id-123")
.via(Channel.WEBSOCKET)
.body("{\"type\":\"update\",\"data\":\"new item\"}")
.send();
Generic Webhook
Send to any HTTP endpoint with custom payload templates. Ideal for PagerDuty, Datadog, or custom APIs.
Module: notify-webhook
notify:
channels:
webhooks:
- name: pagerduty
url: "https://events.pagerduty.com/v2/enqueue"
method: POST
headers:
Authorization: "Token token=${PAGERDUTY_TOKEN}"
payload-template: '{"routing_key":"{{routingKey}}","event_action":"trigger","payload":{"summary":"{{content}}"}}'
WebhookConfig config = WebhookConfig.builder()
.name("pagerduty")
.url("https://events.pagerduty.com/v2/enqueue")
.method("POST")
.headers(Map.of("Authorization", "Token token=..."))
.payloadTemplate("{\"routing_key\":\"R0...\",\"event_action\":\"trigger\",\"payload\":{\"summary\":\"{{content}}\"}}")
.build();
Usage
notify.to("pagerduty")
.via(Channel.custom("pagerduty"))
.param("routingKey", "R0...")
.body("Server web-01 is unreachable")
.send();
Channel.custom("pagerduty") to reference the webhook by its name field.Twitter / X
Post tweets via the Twitter API v2 using OAuth 1.0a authentication.
Module: notify-twitter
notify:
channels:
twitter:
api-key: ${TWITTER_API_KEY}
api-secret: ${TWITTER_API_SECRET}
access-token: ${TWITTER_ACCESS_TOKEN}
access-token-secret: ${TWITTER_ACCESS_TOKEN_SECRET}
TwitterConfig config = TwitterConfig.builder()
.apiKey("key").apiSecret("secret")
.accessToken("token").accessTokenSecret("tokenSecret")
.build();
Getting Credentials
- Go to X Developer Portal and create a project + app
- In Keys and Tokens, generate API Key and API Secret
- Generate Access Token and Access Token Secret with Read and Write permissions
- Make sure your app has OAuth 1.0a enabled under User authentication settings
Usage
notify.via(Channel.TWITTER)
.body("We just launched NotifyHub v2.0! Check it out.")
.send();
Publish posts on LinkedIn via the REST API with OAuth 2.0 Bearer token.
Module: notify-linkedin
notify:
channels:
linkedin:
access-token: ${LINKEDIN_ACCESS_TOKEN}
author-id: "urn:li:person:abc123"
LinkedInConfig config = LinkedInConfig.builder()
.accessToken("token")
.authorId("urn:li:person:abc123")
.build();
Getting Credentials
- Go to LinkedIn Developer Portal and create an app
- In Products, request access to Share on LinkedIn and Sign In with LinkedIn using OpenID Connect
- Use the OAuth 2.0 flow to get an Access Token with scope
w_member_social - Your Author ID is your LinkedIn URN:
urn:li:person:{id}. Get it by callingGET /v2/userinfowith the token
Usage
notify.via(Channel.LINKEDIN)
.body("Excited to announce our new open-source project!")
.send();
Notion
Create pages in Notion databases via the API with an Integration Token.
Module: notify-notion
notify:
channels:
notion:
api-key: ${NOTION_API_KEY}
database-id: ${NOTION_DATABASE_ID}
NotionConfig config = NotionConfig.builder()
.apiKey("secret_...").databaseId("db-id")
.recipients(Map.of("tasks", "another-db-id"))
.build();
Getting Credentials
- Go to Notion Integrations and create a new integration
- Copy the Internal Integration Token (starts with
secret_) - Open the target Notion database, click ... > Connections > Connect to and select your integration
- The Database ID is in the database URL:
notion.so/{workspace}/{database-id}?v=...
subject for the page title and body for the page content.Usage
notify.to("tasks")
.via(Channel.NOTION)
.subject("Bug Report")
.body("Login page returns 500 on invalid email")
.send();
Twitch
Send chat messages via the Twitch Helix API with OAuth 2.0. Supports automatic token refresh.
Module: notify-twitch
notify:
channels:
twitch:
client-id: ${TWITCH_CLIENT_ID}
access-token: ${TWITCH_ACCESS_TOKEN}
refresh-token: ${TWITCH_REFRESH_TOKEN} # Optional: auto-refresh
client-secret: ${TWITCH_CLIENT_SECRET} # Required with refresh-token
broadcaster-id: "836211004"
sender-id: "836211004"
TwitchConfig config = TwitchConfig.builder()
.clientId("tyxy0tn8...")
.accessToken("ck6uie0g...") // Seed token
.refreshToken("pmn0lc2x...") // Optional: enables auto-refresh
.clientSecret("wku9i8gu...") // Required with refreshToken
.broadcasterId("836211004")
.senderId("836211004")
.build();
// getAccessToken() auto-renews 5 minutes before expiry
Getting Credentials
- Go to Twitch Developer Console and register a new application
- Copy the Client ID from the app settings
- Generate an Access Token using the OAuth Authorization Code flow with scopes
user:write:chatanduser:bot - Your Broadcaster ID is your numeric Twitch user ID (find it at Twitch ID Lookup)
- Sender ID is usually the same as Broadcaster ID (your bot account)
Usage
notify.to("836211004")
.via(Channel.TWITCH)
.body("Hello chat! Stream starting soon.")
.send();
refresh-token and client-secret to enable automatic token renewal. Without them, the static access token is used (will expire after ~4 hours).YouTube
Send live chat messages via the YouTube Data API v3. Supports automatic OAuth token refresh.
Module: notify-youtube
notify:
channels:
youtube:
access-token: ${YOUTUBE_ACCESS_TOKEN}
refresh-token: ${YOUTUBE_REFRESH_TOKEN} # Optional: auto-refresh
client-id: ${YOUTUBE_CLIENT_ID} # Required with refresh-token
client-secret: ${YOUTUBE_CLIENT_SECRET} # Required with refresh-token
channel-id: ${YOUTUBE_CHANNEL_ID}
live-chat-id: ${YOUTUBE_LIVE_CHAT_ID}
YouTubeConfig config = YouTubeConfig.builder()
.accessToken("ya29.a0...") // Seed token
.refreshToken("1//0eXyz...") // Optional: enables auto-refresh
.clientId("123.apps.google...") // Required with refreshToken
.clientSecret("GOCSPX-...") // Required with refreshToken
.channelId("UC...")
.liveChatId("Cg0KC...")
.build();
// Token auto-refreshes via Google OAuth2 endpoint
Getting Credentials
- Go to Google Cloud Console and create a project
- Enable the YouTube Data API v3
- Create OAuth 2.0 credentials (Web application type)
- Use the OAuth Playground to generate an Access Token with scope
youtube.force-ssl - Get your Channel ID from YouTube Studio > Settings > Advanced Settings
- The Live Chat ID is obtained from an active livestream via the API:
GET /liveBroadcasts?mine=true
refresh-token, client-id, and client-secret for automatic token renewal. Without them, access tokens expire in ~1 hour.Usage
notify.via(Channel.YOUTUBE)
.body("Thanks for watching! Don't forget to subscribe.")
.send();
Send DMs and publish feed posts via the Instagram Graph API (Meta).
Module: notify-instagram
notify:
channels:
instagram:
access-token: ${INSTAGRAM_ACCESS_TOKEN}
ig-user-id: ${INSTAGRAM_USER_ID}
InstagramConfig config = InstagramConfig.builder()
.accessToken("IGQ...")
.igUserId("17841...")
.recipients(Map.of("support", "user-id"))
.build();
Getting Credentials
- Go to Meta for Developers and select your app
- In Casos de uso, add "Gerenciar mensagens e conteudo no Instagram"
- Link your Instagram Professional account to a Facebook Page
- Generate an Access Token with
instagram_basicandinstagram_manage_messagespermissions via the Graph API Explorer - Get your IG User ID by calling
GET /me?fields=idwith the Instagram token
Usage
notify.to("support")
.via(Channel.INSTAGRAM)
.body("Thanks for reaching out! We'll get back to you soon.")
.send();
SendGrid
Send emails via the SendGrid API with full delivery tracking support. Track delivery status including opened, clicked, bounced, spam complaints, dropped, and deferred events.
Module: notify-sendgrid
notify:
channels:
sendgrid:
api-key: ${SENDGRID_API_KEY}
from: noreply@myapp.com
from-name: MyApp
SendGridConfig config = SendGridConfig.builder()
.apiKey("SG.xxxxxx")
.from("noreply@myapp.com")
.fromName("MyApp")
.build();
Getting Credentials
- Create a free account at SendGrid
- Go to Settings > API Keys and create a new API key with "Mail Send" permission
- Complete Sender Authentication (domain or single sender) to avoid emails going to spam
Usage
// Send and get provider message ID for tracking
SendResult result = notify.to("user@example.com")
.via(Channel.custom("sendgrid"))
.subject("Welcome to MyApp!")
.body("Thanks for signing up.")
.sendWithResult();
String providerMessageId = result.getProviderMessageId();
Delivery Tracking
SendGrid supports advanced email delivery tracking. Check the status of a sent email:
// Check email delivery status
EmailStatusProvider sendgrid = (EmailStatusProvider) hub.getChannel("sendgrid");
DeliveryStatus status = sendgrid.checkEmailStatus(providerMessageId);
// Returns: DELIVERED, OPENED, CLICKED, BOUNCED, SPAM_COMPLAINT, DROPPED, DEFERRED
DELIVERED, OPENED, CLICKED, BOUNCED, SPAM_COMPLAINT, DROPPED, and DEFERRED. Use the MCP tool check_email_status to query these from an AI assistant.TikTok Shop
Send notifications via the TikTok Shop API with HMAC-SHA256 signed requests for secure communication.
Module: notify-tiktok-shop
notify:
channels:
tiktok-shop:
app-key: ${TIKTOK_SHOP_APP_KEY}
app-secret: ${TIKTOK_SHOP_APP_SECRET}
access-token: ${TIKTOK_SHOP_ACCESS_TOKEN}
shop-id: ${TIKTOK_SHOP_ID}
TikTokShopConfig config = TikTokShopConfig.builder()
.appKey("app-key")
.appSecret("app-secret")
.accessToken("access-token")
.shopId("shop-id")
.build();
Getting Credentials
- Go to TikTok Shop Partner Center and register as a developer
- Create an app to get your App Key and App Secret
- Authorize the app for a shop to get the Access Token
- Your Shop ID is found in the TikTok Shop Seller Center dashboard
Usage
notify.to("shop-id")
.via(Channel.TIKTOK_SHOP)
.body("Your order has been shipped!")
.send();
app-secret. No manual signing required.Post to Facebook Page feeds and send Messenger DMs via the Facebook Graph API.
Module: notify-facebook
notify:
channels:
facebook:
page-access-token: ${FACEBOOK_PAGE_ACCESS_TOKEN}
page-id: ${FACEBOOK_PAGE_ID}
FacebookConfig config = FacebookConfig.builder()
.pageAccessToken("EAAGx...")
.pageId("123456789")
.build();
Getting Credentials
- Go to Meta for Developers and create or select your app
- In Casos de uso, add "Interagir com clientes no Messenger"
- Go to your app's Settings > Basic to find the App ID and App Secret
- Link a Facebook Page to the app and generate a Page Access Token via the Graph API Explorer
- The Page ID is visible on your Page's "About" section or via the Graph API
/oauth/access_token endpoint. Short-lived tokens expire in ~1 hour.Usage — Page Feed Post
// Post to page feed — use "feed" as recipient
notify.to("feed")
.via(Channel.FACEBOOK)
.body("Exciting news! Our v2.0 release is live.")
.send();
Usage — Messenger DM
// Send Messenger DM — use PSID as recipient
notify.to("1234567890123456")
.via(Channel.FACEBOOK)
.body("Thanks for contacting us! We'll reply shortly.")
.send();
"feed" as the recipient to publish a post on your Page's feed. Use a Page-Scoped ID (PSID) to send a Messenger DM to a specific user.AWS SNS
Publish notifications to Amazon SNS topics, endpoints (push), or phone numbers (SMS) via the AWS SDK v2.
Module: notify-aws-sns
notify:
channels:
aws-sns:
region: us-east-1
access-key-id: ${AWS_ACCESS_KEY_ID}
secret-access-key: ${AWS_SECRET_ACCESS_KEY}
topic-arn: arn:aws:sns:us-east-1:123456789:my-topic # optional
AwsSnsConfig config = AwsSnsConfig.builder()
.region("us-east-1")
.accessKeyId("AKIA...")
.secretAccessKey("secret...")
.topicArn("arn:aws:sns:us-east-1:123456789:my-topic")
.build();
Getting Credentials
- Sign in to the AWS SNS Console
- Create a Topic (Standard or FIFO) and copy the Topic ARN
- Go to IAM Console and create a user with
AmazonSNSFullAccesspolicy - Generate an Access Key ID and Secret Access Key for the IAM user
- Set the Region to match your topic's region (e.g.,
us-east-1)
Usage
// Publish to a topic (all subscribers receive it)
notify.to("all-users")
.via(Channel.AWS_SNS)
.subject("Deployment Complete")
.body("Version 2.0 deployed successfully.")
.send();
// Send SMS directly via SNS (recipient = phone number)
notify.to("+5511999887766")
.via(Channel.AWS_SNS)
.body("Your verification code is 123456")
.send();
// Publish to a specific endpoint ARN
notify.to("arn:aws:sns:us-east-1:123:endpoint/GCM/myapp/abc123")
.via(Channel.AWS_SNS)
.body("Push notification via SNS")
.send();
+5511...) for direct SMS, or any string when a default topic-arn is configured.Mailgun
Send transactional and marketing emails via the Mailgun REST API with US and EU region support.
Module: notify-mailgun
notify:
channels:
mailgun:
api-key: ${MAILGUN_API_KEY}
domain: mg.myapp.com
from: noreply@myapp.com
region: US # US (default) or EU
MailgunConfig config = MailgunConfig.builder()
.apiKey("key-abc123...")
.domain("mg.myapp.com")
.from("noreply@myapp.com")
.region("EU") // optional, defaults to "US"
.build();
Getting Credentials
- Sign up at Mailgun and verify your account
- Go to Sending > Domains and add your domain (e.g.,
mg.myapp.com) - Follow the DNS verification steps (add TXT, MX, and CNAME records)
- Go to API Security and copy your API Key (starts with
key-) - Set the From address to an email on your verified domain
region: EU to use Mailgun's EU endpoint (api.eu.mailgun.net). The sandbox domain (sandboxXXX.mailgun.org) works for testing without DNS setup.Usage
notify.to("user@example.com")
.via(Channel.MAILGUN)
.subject("Welcome to MyApp!")
.body("Thanks for signing up. Get started at https://myapp.com/start")
.send();
PagerDuty
Trigger incidents on PagerDuty via the Events API v2. Supports severity levels and automatic summary truncation.
Module: notify-pagerduty
notify:
channels:
pagerduty:
routing-key: ${PAGERDUTY_ROUTING_KEY}
severity: warning # critical, error, warning (default), info
PagerDutyConfig config = PagerDutyConfig.builder()
.routingKey("R0KEY...")
.severity("critical") // optional, defaults to "warning"
.build();
Getting Credentials
- Sign in to PagerDuty
- Go to Services > Service Directory and create a new service (or select an existing one)
- In the service's Integrations tab, click "Add Integration"
- Select "Events API v2" as the integration type
- Copy the Integration Key (this is the
routing-key)
Usage
// Trigger an incident
notify.to("ops-team")
.via(Channel.PAGERDUTY)
.subject("Database connection pool exhausted")
.body("Pool usage at 100% for 5 minutes. Auto-scaling failed.")
.send();
// With template
notify.to("sre-team")
.via(Channel.PAGERDUTY)
.template("infra-alert")
.param("service", "api-gateway")
.param("metric", "p99 latency > 2s")
.send();
recipient field is informational for PagerDuty — routing is determined by the routing-key which maps to a specific PagerDuty service.Kick
Send chat messages to Kick channels via the official Kick Public API. Supports bot and user message types with OAuth 2.1 authentication.
Module: notify-kick
notify:
channels:
kick:
client-id: ${KICK_CLIENT_ID}
access-token: ${KICK_ACCESS_TOKEN}
broadcaster-id: ${KICK_BROADCASTER_ID}
message-type: bot # bot (default) or user
KickConfig config = KickConfig.builder()
.clientId("your-client-id")
.accessToken("your-oauth-access-token")
.broadcasterId("12345678")
.messageType("bot") // optional, defaults to "bot"
.build();
Getting Credentials
- Go to kick.com/settings/developer and register your application
- Enable Two-Factor Authentication (2FA) on your Kick account if not already enabled (required for API access)
- Copy your Client ID and Client Secret from the developer settings
- Generate an OAuth 2.1 access token with the
chat:writescope - Find your Broadcaster ID via the Kick API or your channel settings
client-id, client-secret, and refresh-token and the channel will handle re-authentication transparently.API Details
- Endpoint:
POST https://api.kick.com/public/v1/chat - Maximum message length: 500 characters (messages are automatically truncated)
- Message types:
bot(sent as bot/app) oruser(sent as the authenticated user)
Usage
// Send a chat message to your Kick channel
notify.to("my-kick-channel")
.via(Channel.KICK)
.body("Stream is going live in 5 minutes! 🎮")
.send();
// With template
notify.to("my-kick-channel")
.via(Channel.KICK)
.template("stream-alert")
.param("game", "Minecraft")
.param("viewers", "1234")
.send();
recipient field is used as a label for routing purposes — the actual target channel is determined by the broadcaster-id configured in your Kick settings.Custom Channels
Implement the NotificationChannel interface to create your own channel:
public class MyCustomChannel implements NotificationChannel {
public String getName() { return "my-custom"; }
public boolean isAvailable() { return apiKey != null; }
public void send(Notification notification) throws NotificationSendException {
// Your implementation here
}
}
Register it:
// Programmatically
hub.registerChannel(new MyCustomChannel(config));
// Or as a Spring Bean
@Bean
public MyCustomChannel myCustomChannel() {
return new PagerDutyChannel(config);
}
Use it with Channel.custom("pagerduty").
Templates
NotifyHub uses Mustache as its built-in template engine. Templates support HTML and plain-text variants, i18n, and versioning for A/B testing.
Directory Structure
src/main/resources/templates/notify/
order-confirmed/
html.mustache # Email HTML variant
txt.mustache # SMS/WhatsApp text variant
welcome/
html.mustache
txt.mustache
alert/
txt.mustache
Usage
notify.to(user)
.via(Channel.EMAIL)
.template("order-confirmed")
.param("customerName", "Gabriel")
.param("orderId", "ORD-123")
.locale(Locale.PT_BR) // i18n support
.templateVersion("v2") // A/B testing
.send();
TemplateEngine Interface
public interface TemplateEngine {
String render(String templateName, String variant, Map<String, Object> params);
String render(String templateName, String variant, Map<String, Object> params, Locale locale);
boolean exists(String templateName, String variant);
}
Built-in implementations: MustacheTemplateEngine and VersionedTemplateEngine.
Retry & Error Handling
Retry Policies
RetryPolicy.none() // Fail immediately
RetryPolicy.fixed(3, Duration.ofSeconds(2)) // 3 attempts, 2s delay
RetryPolicy.exponential(3) // 3 attempts, 1s/2s/4s backoff
RetryPolicy.exponential(5, Duration.ofMillis(500)) // Custom initial delay
Methods: getMaxAttempts(), getInitialDelay(), getStrategy() (NONE/FIXED/EXPONENTIAL), getDelayForAttempt(int), shouldRetry().
Override per notification
notify.to(user).via(Channel.EMAIL)
.retry(5) // Override retry count for this send
.content("Important!").send();
Exception Types
| Exception | When |
|---|---|
NotificationSendException | Channel send failure (network, auth, payload error) |
OAuthTokenRefreshException | OAuth token refresh failure (YouTube/Twitch) |
RateLimitExceededException | Rate limit exceeded for a channel |
TemplateNotFoundException | Template name not found |
Rate Limiting
Built-in TokenBucketRateLimiter with per-channel API-aware presets that match real API limits:
| Channel | Limit | Window |
|---|---|---|
| YouTube | 180 requests | 1 day |
| Twitch | 20 requests | 30 seconds |
| Twitter/X | 300 requests | 3 hours |
| Slack | 1 request | 1 second |
| Telegram | 30 requests | 1 second |
| Discord | 30 requests | 1 minute |
| 100 requests | 1 day | |
| 200 requests | 1 hour | |
| Google Chat | 60 requests | 1 minute |
Factory Methods
RateLimitConfig.youtube() // Factory method per channel
RateLimitConfig.forChannel("twitch") // Lookup by name
RateLimitConfig.allDefaults() // Map of all presets
RateLimitConfig.perSecond(10) // Custom
RateLimitConfig.perMinute(60) // Custom
Priority.URGENT bypasses rate limiting entirely. Use it only for critical alerts.Delivery Tracking
DeliveryReceipt
DeliveryReceipt receipt = notify.to(user)
.via(Channel.EMAIL)
.template("welcome")
.sendTracked();
receipt.getId(); // UUID
receipt.getChannelName(); // "email"
receipt.getRecipient(); // "user@example.com"
receipt.getStatus(); // PENDING, SCHEDULED, SENT, FAILED, CANCELLED
receipt.getTimestamp(); // Instant
receipt.getErrorMessage(); // null or error details
Status Values
| Status | Description |
|---|---|
PENDING | Notification created, not yet sent. |
SCHEDULED | Scheduled for future delivery. |
SENT | Successfully delivered. |
FAILED | Failed after all retry attempts. |
CANCELLED | Cancelled before delivery. |
Dead Letter Queue (DLQ)
Failed notifications (after exhausting all retries) are captured in the DLQ for inspection and replay:
// Access DLQ
List<DeadLetter> failures = hub.getDeadLetterQueue().findAll();
// DeadLetter contains
deadLetter.getChannelName(); // "email"
deadLetter.getRecipient(); // "user@example.com"
deadLetter.getSubject(); // "Welcome"
deadLetter.getContent(); // "Hello!"
deadLetter.getErrorMessage(); // "Connection refused"
deadLetter.getAttemptCount(); // 3
Audience & Contacts
Create Contacts
Contact vip = Contact.builder()
.name("Gabriel")
.email("gabriel@example.com")
.phone("+5548999999999")
.tag("vip").tag("plan:premium")
.metadata("region", "south")
.build();
Create Audiences
Audiences are defined by tag filters using AND logic — contacts must match ALL tags to be included:
// Create audience (AND logic on tags)
hub.getAudienceManager().createAudience("premium-users", Set.of("vip", "plan:premium"));
// Send to audience
notify.toAudience("premium-users")
.via(Channel.EMAIL)
.template("exclusive-offer")
.param("discount", "30%")
.send();
Scheduling
Schedule notifications for future delivery with delay-based or absolute-time scheduling. List pending notifications and cancel them before delivery.
Schedule with Delay
// Schedule with relative delay
notify.to(user)
.via(Channel.PUSH)
.content("Don't forget your appointment tomorrow!")
.schedule(Duration.ofHours(24));
Schedule at Specific Time
// Schedule at absolute time
notify.to(user)
.via(Channel.EMAIL)
.template("weekly-digest")
.scheduleAt(Instant.parse("2024-12-25T09:00:00Z"));
List Scheduled Notifications
// List all scheduled notifications
List<ScheduledNotification> pending = hub.getScheduledNotifications();
// Filter by status
List<ScheduledNotification> sent = hub.getScheduledNotifications("SENT");
Cancel Scheduled Notifications
// Cancel before delivery
hub.cancelScheduledNotification(notificationId);
Configuration
notify:
scheduling:
enabled: true
pool-size: 2
MCP / AI Usage
Use the scheduling tools from any AI assistant:
"Schedule an email to user@test.com in 30 minutes reminding about the meeting"
"Schedule a Slack notification for tomorrow at 9am saying standup starts soon"
"List all scheduled notifications"
"Cancel the scheduled notification with ID abc-123"
schedule_notification with either delay (e.g., "5m", "1h30m") or scheduled_at (ISO 8601) for flexible scheduling. Use list_scheduled_notifications and cancel_scheduled_notification to manage pending deliveries.Event Listeners
Implement the NotificationListener interface to react to notification lifecycle events:
public class MyListener implements NotificationListener {
public void onSuccess(String channel, String template) {
log.info("Sent via {} using {}", channel, template);
}
public void onFailure(String channel, String template, Exception e) {
log.error("Failed on {}: {}", channel, e.getMessage());
}
public void onScheduled(String channel, String recipient, Duration delay) {
log.info("Scheduled {} to {} in {}", channel, recipient, delay);
}
public void onCancelled(String channel, String recipient) {
log.info("Cancelled {} to {}", channel, recipient);
}
}
Register via builder: .listener(new MyListener()) or as a Spring Bean.
notify:
events:
enabled: true # Enables Spring event publishing
Circuit Breaker
Per-channel circuit breaker prevents cascading failures. When a channel fails repeatedly, the circuit opens and short-circuits further attempts until a recovery period elapses.
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();
notify:
circuit-breaker:
enabled: true
failure-threshold: 5
open-duration: 30s
window-size: 60s
States: CLOSED (normal) → OPEN (rejecting all sends) → HALF_OPEN (testing recovery with one attempt). The Actuator health endpoint includes circuit breaker state per channel.
You can also add bulkhead isolation to limit concurrent sends per channel:
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. Combine with delivery tracking to check open/click status.
A/B Testing
Built-in deterministic A/B testing for notifications. Variant assignment uses SHA-256 hashing — 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 — no external A/B testing service needed.
Quiet Hours
Respect user preferences for notification timing. Implement on your Notifiable:
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.
Test Utilities
TestNotifyHub provides a test-friendly wrapper with capturing channels for all built-in channel types. No mocking needed:
@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");
}
@AfterEach
void cleanup() {
test.reset(); // Clear captured notifications between tests
}
TestNotifyHub registers capturing channels for every Channel enum value. Query with sent() (all) or sent("email") (by channel).
Spring Boot Integration
Injection
@Service
public class MyService {
private final NotifyHub notify;
public MyService(NotifyHub notify) { this.notify = notify; }
}
Auto-configured channels appear as beans. Only channels with their required properties set are activated.
Full application.yml Reference
notify:
channels:
email:
host: smtp.gmail.com
port: 587
username: ${GMAIL_USER}
password: ${GMAIL_PASS}
from: noreply@myapp.com
from-name: MyApp
tls: true
sms:
account-sid: ${TWILIO_ACCOUNT_SID}
auth-token: ${TWILIO_AUTH_TOKEN}
from-number: "+15551234567"
whatsapp:
account-sid: ${TWILIO_ACCOUNT_SID}
auth-token: ${TWILIO_AUTH_TOKEN}
from-number: "+14155238886"
slack:
webhook-url: ${SLACK_WEBHOOK_URL}
recipients:
engineering: "https://hooks.slack.com/services/..."
telegram:
bot-token: ${TELEGRAM_BOT_TOKEN}
chat-id: ${TELEGRAM_CHAT_ID}
discord:
webhook-url: ${DISCORD_WEBHOOK_URL}
username: NotifyBot
teams:
webhook-url: ${TEAMS_WEBHOOK_URL}
push:
server-key: ${FIREBASE_SERVER_KEY}
websocket:
uri: wss://ws.example.com/notifications
reconnect-enabled: true
reconnect-delay-ms: 3000
max-reconnect-attempts: 10
google-chat:
webhook-url: ${GOOGLE_CHAT_WEBHOOK_URL}
twitter:
api-key: ${TWITTER_API_KEY}
api-secret: ${TWITTER_API_SECRET}
access-token: ${TWITTER_ACCESS_TOKEN}
access-token-secret: ${TWITTER_ACCESS_TOKEN_SECRET}
linkedin:
access-token: ${LINKEDIN_ACCESS_TOKEN}
author-id: "urn:li:person:abc123"
notion:
api-key: ${NOTION_API_KEY}
database-id: ${NOTION_DATABASE_ID}
twitch:
client-id: ${TWITCH_CLIENT_ID}
access-token: ${TWITCH_ACCESS_TOKEN}
refresh-token: ${TWITCH_REFRESH_TOKEN}
client-secret: ${TWITCH_CLIENT_SECRET}
broadcaster-id: "836211004"
sender-id: "836211004"
youtube:
access-token: ${YOUTUBE_ACCESS_TOKEN}
refresh-token: ${YOUTUBE_REFRESH_TOKEN}
client-id: ${YOUTUBE_CLIENT_ID}
client-secret: ${YOUTUBE_CLIENT_SECRET}
channel-id: ${YOUTUBE_CHANNEL_ID}
live-chat-id: ${YOUTUBE_LIVE_CHAT_ID}
instagram:
access-token: ${INSTAGRAM_ACCESS_TOKEN}
ig-user-id: ${INSTAGRAM_USER_ID}
sendgrid:
api-key: ${SENDGRID_API_KEY}
from: noreply@myapp.com
from-name: MyApp
tiktok-shop:
app-key: ${TIKTOK_SHOP_APP_KEY}
app-secret: ${TIKTOK_SHOP_APP_SECRET}
access-token: ${TIKTOK_SHOP_ACCESS_TOKEN}
shop-id: ${TIKTOK_SHOP_ID}
facebook:
page-access-token: ${FACEBOOK_PAGE_ACCESS_TOKEN}
page-id: ${FACEBOOK_PAGE_ID}
aws-sns:
region: us-east-1
access-key-id: ${AWS_ACCESS_KEY_ID}
secret-access-key: ${AWS_SECRET_ACCESS_KEY}
topic-arn: ${AWS_SNS_TOPIC_ARN}
mailgun:
api-key: ${MAILGUN_API_KEY}
domain: mg.myapp.com
from: noreply@myapp.com
region: US
pagerduty:
routing-key: ${PAGERDUTY_ROUTING_KEY}
severity: warning
retry:
max-attempts: 3
strategy: exponential # none, fixed, exponential
scheduling:
enabled: true
pool-size: 2
tracking:
enabled: true
type: memory # memory or jpa
dlq-enabled: true
events:
enabled: true
rate-limit:
enabled: true
use-defaults: true # Use API-aware presets per channel
max-requests: 100 # Global default
window: 60s
channels: # Per-channel overrides
slack:
max-requests: 2
window: 1s
deduplication:
enabled: true
ttl: 24h
strategy: content-hash # content-hash or explicit-key
audit:
enabled: true
type: memory
audience:
enabled: true
status-webhook:
url: "https://myapp.com/notify/status"
timeout-ms: 10000
signing-secret: ${WEBHOOK_SIGNING_SECRET}
REST API / Swagger
NotifyHub includes a fully documented REST API with Swagger UI (OpenAPI 3.0). When running the demo or Docker image, access the interactive API at:
# Swagger UI (interactive)
http://localhost:8080/swagger-ui.html
# OpenAPI JSON spec
http://localhost:8080/v3/api-docs
Available Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /api/info | API info and registered channels |
POST | /send/email | Send email via SMTP |
POST | /send/template | Send email with Mustache template |
POST | /send/notifiable | Send to a Notifiable entity |
POST | /send/sms | Send SMS via Twilio |
POST | /send/whatsapp | Send WhatsApp via Twilio |
POST | /send/telegram | Send Telegram message |
POST | /send/discord | Send Discord message |
POST | /send/slack | Send Slack message |
POST | /send/teams | Send Microsoft Teams message |
POST | /send/google-chat | Send Google Chat message |
POST | /send/push | Send Firebase push notification |
POST | /send/websocket | Send WebSocket message |
POST | /send/twitter | Post tweet on Twitter/X |
POST | /send/linkedin | Publish LinkedIn post |
POST | /send/notion | Create Notion page |
POST | /send/multi | Send to multiple channels at once |
POST | /send/fallback | Test fallback chain |
POST | /send/tracked | Send with delivery tracking |
POST | /send/scheduled | Schedule notification for future |
POST | /send/audience | Send to audience segment |
GET | /tracking | Delivery tracking history |
GET | /scheduled | List scheduled notifications |
DELETE | /scheduled/{id} | Cancel scheduled notification |
POST | /contacts | Create contact with tags |
GET | /contacts | List all contacts |
POST | /audiences | Create audience by tags |
Example
# Send email
curl -X POST "localhost:8080/send/email?to=user@example.com&subject=Hello&body=Hi+from+NotifyHub"
# Send to Slack
curl -X POST "localhost:8080/send/slack?channel=%23general&message=Deploy+complete"
# Send multi-channel (email + slack)
curl -X POST "localhost:8080/send/multi?email=admin@test.com&slackChannel=%23alerts"
# Schedule notification (30s delay)
curl -X POST "localhost:8080/send/scheduled?to=user@test.com&delaySeconds=30&subject=Reminder"
http://localhost:8080/swagger-ui.html in your browser to explore and test all endpoints interactively — no code needed.Admin Dashboard
NotifyHub ships with a built-in web dashboard at /notify-admin. It provides real-time monitoring, delivery tracking, analytics, and dead letter management — all in a dark/light theme UI with responsive design.
Enable
Add the notify-admin module to your project:
<dependency>
<groupId>io.notifyhub</groupId>
<artifactId>notify-admin</artifactId>
</dependency>
That's it. Spring Boot auto-configuration registers the admin controller automatically. Access the dashboard at:
http://localhost:8080/notify-admin
Pages
The dashboard includes 8 pages, each accessible via the sidebar navigation:
| Page | Route | Description |
|---|---|---|
| Dashboard | /notify-admin | Overview with metric cards (sent, failed, pending, dead letters, active channels), recent activity feed, system status indicators, and registered channels with health check. |
| Analytics | /notify-admin/analytics | Chart.js charts — volume over time, distribution by channel, success rate, and hourly heatmap. |
| Tracking | /notify-admin/tracking | List every delivery receipt with timestamp, channel, recipient, and status. Filter by channel. |
| Dead Letter Queue | /notify-admin/dlq | Notifications that failed after all retry attempts. Inspect error details and remove/reprocess entries. |
| Channels | /notify-admin/channels | All registered channels with availability status and implementation type. |
| Audit Log | /notify-admin/audit-log | Complete event history — SENT, FAILED, SCHEDULED, CANCELLED. Filter by event type. |
| Audiences | /notify-admin/audiences | Manage contacts and audience segments with tag-based filtering. |
| Status Webhook | /notify-admin/status-webhook | View webhook configuration, recent deliveries, and payload details. |
Analytics API
The dashboard exposes JSON API endpoints used by the charts, which you can also consume programmatically:
| Method | Endpoint | Description |
|---|---|---|
GET | /notify-admin/api/stats/overview | Count by status (SENT, FAILED, PENDING, etc.) |
GET | /notify-admin/api/stats/by-channel | Messages per channel |
GET | /notify-admin/api/stats/timeline?days=7 | Sent/failed per day for the last N days |
GET | /notify-admin/api/stats/errors | Top 10 error messages |
Features
- Dark / Light theme — toggle in the sidebar, preference saved in localStorage
- Responsive — works on desktop, tablet, and mobile
- Real-time data — pages read directly from NotifyHub's in-memory tracker, DLQ, and audit log
- Zero config — just add the dependency and features are auto-detected (tracking, DLQ, audit, audiences, webhook)
- System status — dashboard shows green/red indicators for each subsystem (tracking, DLQ, audit log, audiences, webhook)
Docker
Don't use Java? Run NotifyHub as a REST API or MCP Server with Docker. Just set environment variables for the channels you need.
REST API
docker run -d -p 8080:8080 \
-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-api
# Send email
curl -X POST "localhost:8080/send/email?to=user@test.com&subject=Hi&body=Hello!"
# Send Discord
curl -X POST "localhost:8080/send/discord?message=Deploy done!"
# Swagger UI
open http://localhost:8080/swagger-ui.html
MCP Server
{
"mcpServers": {
"notifyhub": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "NOTIFY_CHANNELS_EMAIL_HOST=smtp.gmail.com",
"-e", "NOTIFY_CHANNELS_EMAIL_FROM=you@gmail.com",
"gabrielbbal10/notifyhub-mcp"
]
}
}
}
Environment Variables
Both Docker images use NOTIFY_CHANNELS_* environment variables. Only set the channels you need:
| Variable | Example |
|---|---|
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_EMAIL_FROM | you@gmail.com |
NOTIFY_CHANNELS_SLACK_WEBHOOK_URL | https://hooks.slack.com/... |
NOTIFY_CHANNELS_TELEGRAM_BOT_TOKEN | 123456:ABC-DEF |
NOTIFY_CHANNELS_TELEGRAM_CHAT_ID | 987654321 |
NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL | https://discord.com/api/webhooks/... |
NOTIFY_CHANNELS_SMS_ACCOUNT_SID | AC... |
NOTIFY_CHANNELS_SMS_AUTH_TOKEN | token |
NOTIFY_CHANNELS_SMS_FROM_NUMBER | +15551234567 |
NOTIFY_CHANNELS_SENDGRID_API_KEY | SG.xxxxxx |
NOTIFY_CHANNELS_SENDGRID_FROM | noreply@myapp.com |
NOTIFY_CHANNELS_TIKTOK_SHOP_APP_KEY | app-key |
NOTIFY_CHANNELS_TIKTOK_SHOP_APP_SECRET | app-secret |
NOTIFY_CHANNELS_FACEBOOK_PAGE_ACCESS_TOKEN | EAAGx... |
NOTIFY_CHANNELS_FACEBOOK_PAGE_ID | 123456789 |
MCP Server
NotifyHub includes an MCP (Model Context Protocol) server with 33 tools for AI agent integration. Connect it to Claude Desktop, Claude Code, Cursor, or any MCP-compatible client.
All 33 Tools
| Category | Tool | Description |
|---|---|---|
| Channel Send | send_email | Send via SMTP |
send_sms | Send via Twilio | |
send_whatsapp | Send via Twilio WhatsApp | |
send_slack | Send via webhook | |
send_telegram | Send via Bot API | |
send_discord | Send via webhook | |
send_teams | Send via webhook | |
send_google_chat | Send via webhook | |
send_push | Firebase Cloud Messaging | |
send_twitter | Twitter/X API v2 | |
send_linkedin | LinkedIn REST API | |
send_notion | Notion Database API | |
send_twitch | Twitch Helix API | |
send_youtube | YouTube Live Chat | |
send_instagram | Instagram Graph API | |
send_tiktok_shop | TikTok Shop API (HMAC-SHA256) | |
send_facebook | Facebook Graph API (Page + Messenger) | |
| Unified | send_notification | Send via any channel |
send_batch | Same message to multiple recipients | |
send_multi_channel | Same message across multiple channels | |
send_to_audience | Send to a named audience | |
| Contacts | create_contact | Create contact with tags |
list_contacts | List/filter contacts | |
create_audience | Create audience by tag filter | |
list_audiences | List audiences | |
| Scheduling | schedule_notification | Schedule for future delivery (delay or absolute time) |
list_scheduled_notifications | List pending/sent/cancelled scheduled notifications | |
cancel_scheduled_notification | Cancel a pending scheduled notification | |
| Monitoring | list_channels | List configured channels |
list_delivery_receipts | View delivery history | |
list_dead_letters | View failed notifications | |
get_analytics | Delivery analytics and stats | |
check_email_status | Check email delivery status (SendGrid: delivered, opened, bounced, etc.) |
Example Prompts
Once connected, you can use natural language:
- "Send an email to user@test.com saying the deploy is complete"
- "Create a VIP audience with tags 'premium' and 'active', then send them a promo email"
- "Send an SMS to +5548999999999 with the verification code"
- "Notify the team on Slack and Telegram that the build passed"
- "Show me the delivery analytics — how many sent vs failed?"
- "List all dead letters and show me what failed"
- "Schedule an email to the team for tomorrow at 9am about the standup"
- "Check if that email to john@test.com was delivered or bounced"
- "Post on our Facebook page that the sale starts today"
OAuth Token Refresh
NotifyHub supports automatic OAuth 2.0 token refresh for YouTube and Twitch. The OAuthTokenManager is thread-safe using ReentrantReadWriteLock and proactively refreshes tokens 5 minutes before expiry.
OAuthTokenManager manager = OAuthTokenManager.builder()
.tokenEndpoint("https://oauth2.googleapis.com/token")
.refreshToken("1//0eXyz...")
.clientId("client-id")
.clientSecret("client-secret")
.initialAccessToken("ya29...") // Optional seed token
.refreshBufferSeconds(300) // Default: 300 (5 min before expiry)
.timeoutSeconds(10) // HTTP timeout
.build();
String token = manager.getAccessToken(); // Auto-refresh if needed
manager.forceRefresh(); // Force refresh (e.g., after 401)
refreshToken + clientSecret (and clientId for YouTube) in the channel config. If only a static accessToken is set, behavior is backward-compatible with no refresh.How It Works
getAccessToken()acquires a read lock. If token is valid, returns immediately.- If token is expired or within 5 minutes of expiry, upgrades to write lock and performs refresh.
- Only one thread refreshes at a time — others wait and receive the new token.
- Uses
java.net.http.HttpClient(JDK) — zero external dependencies.
Message Queues
For high-throughput or distributed systems, enqueue notifications to RabbitMQ or Kafka. A consumer picks them up and sends via NotifyHub automatically.
RabbitMQ
Module: notify-queue-rabbitmq
notify:
queue:
rabbitmq:
enabled: true
queue-name: notifyhub-notifications
exchange-name: notifyhub-exchange
routing-key: notification
Apache Kafka
Module: notify-queue-kafka
notify:
queue:
kafka:
enabled: true
topic: notifyhub-notifications
consumer:
group-id: notifyhub-group
Testing
Running Tests
# All modules
mvn test
# Specific module
mvn test -pl notify-core
mvn test -pl notify-channels/notify-youtube
mvn test -pl notify-spring-boot-starter
Test Patterns
- JUnit 5 with
@DisplayNameannotations for readable test names. - Config builder validation tests verify required fields throw
IllegalArgumentException. - Network-dependent tests use short timeouts and expect connection exceptions.
- Thread safety tests use concurrent threads to verify atomic operations (e.g.,
OAuthTokenManager). - All tests are self-contained — no external services required.
Project Modules
| Module | Description |
|---|---|
notify-core | Core API, interfaces, fluent builder, templates, retry, rate limiting, DLQ, audiences. |
notify-email | SMTP channel (JavaMail). |
notify-sms | SMS + WhatsApp (Twilio). |
notify-slack | Slack webhooks. |
notify-telegram | Telegram Bot API. |
notify-discord | Discord webhooks. |
notify-teams | Microsoft Teams webhooks. |
notify-push-firebase | Firebase Cloud Messaging. |
notify-webhook | Generic HTTP webhooks. |
notify-websocket | Java WebSocket (java.net.http). |
notify-google-chat | Google Chat webhooks. |
notify-twitter | Twitter/X API v2. |
notify-linkedin | LinkedIn REST API. |
notify-notion | Notion Database API. |
notify-twitch | Twitch Helix API. |
notify-youtube | YouTube Data API v3. |
notify-instagram | Instagram Graph API. |
notify-sendgrid | SendGrid Email API (with delivery tracking). |
notify-tiktok-shop | TikTok Shop API (HMAC-SHA256). |
notify-facebook | Facebook Graph API (Page + Messenger). |
notify-spring-boot-starter | Auto-configuration + properties. |
notify-mcp | MCP Server for AI assistants (33 tools). |
notify-demo | Demo application. |