{
  "markdown": "<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/logo-white.png\"/>\n    <img src=\"docs/logo.png\" alt=\"NotifyHub\" width=\"180\"/>\n  </picture>\n</p>\n\n<h1 align=\"center\">NotifyHub</h1>\n\n<p align=\"center\">\n  <strong>One API. Every channel.</strong><br/>\n  Unified notification library for Java and Spring Boot.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://openjdk.org/\"><img src=\"https://img.shields.io/badge/Java-17%2B-blue\" alt=\"Java 17+\"/></a>\n  <a href=\"https://spring.io/projects/spring-boot\"><img src=\"https://img.shields.io/badge/Spring%20Boot-3.x-green\" alt=\"Spring Boot 3.x\"/></a>\n  <a href=\"https://central.sonatype.com/namespace/io.github.gabrielbbaldez\"><img src=\"https://img.shields.io/maven-central/v/io.github.gabrielbbaldez/notify-spring-boot-starter\" alt=\"Maven Central\"/></a>\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\"/></a>\n  <a href=\"https://github.com/GabrielBBaldez/notify-hub/actions/workflows/ci.yml\"><img src=\"https://github.com/GabrielBBaldez/notify-hub/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"/></a>\n  <a href=\"https://codecov.io/gh/GabrielBBaldez/notify-hub\"><img src=\"https://codecov.io/gh/GabrielBBaldez/notify-hub/graph/badge.svg\" alt=\"codecov\"/></a>\n\n</p>\n\n<p align=\"center\">\n  <a href=\"https://gabrielbbaldez.github.io/notify-hub/\"><strong>Website</strong></a> &nbsp;&middot;&nbsp;\n  <a href=\"https://gabrielbbaldez.github.io/notify-hub/docs.html\"><strong>Documentation</strong></a> &nbsp;&middot;&nbsp;\n  <a href=\"https://gabrielbbaldez.github.io/notify-hub/docs.html#getting-started\"><strong>Getting Started</strong></a>\n</p>\n\n---\n\nStop 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.\n\n```java\nnotify.to(user)\n    .via(EMAIL)\n    .fallback(SMS)\n    .priority(Priority.HIGH)\n    .subject(\"Order confirmed\")\n    .template(\"order-confirmed\")\n    .param(\"orderId\", order.getId())\n    .attach(invoicePdf)\n    .send();\n```\n\n---\n\n## Why NotifyHub?\n\n| | Problem | Without NotifyHub | With NotifyHub |\n|:-:|---------|------------------|----------------|\n| <img src=\"https://cdn.simpleicons.org/gmail\" width=\"16\"> | Email | JavaMail config, MIME types, Session... | `.via(EMAIL)` |\n| 📱 | SMS | Twilio SDK, different API entirely | `.via(SMS)` |\n| <img src=\"https://cdn.simpleicons.org/whatsapp\" width=\"16\"> | WhatsApp | Another Twilio setup, prefix logic | `.via(WHATSAPP)` |\n| <img src=\"https://cdn.simpleicons.org/slack\" width=\"16\"> | Slack | Webhook HTTP, JSON payload | `.via(SLACK)` |\n| <img src=\"https://cdn.simpleicons.org/telegram\" width=\"16\"> | Telegram | Bot API, HTTP client setup | `.via(TELEGRAM)` |\n| <img src=\"https://cdn.simpleicons.org/discord\" width=\"16\"> | Discord | Webhook HTTP, JSON payload | `.via(DISCORD)` |\n| 👥 | Teams | Incoming Webhook, MessageCard JSON | `.via(TEAMS)` |\n| <img src=\"https://cdn.simpleicons.org/firebase\" width=\"16\"> | Push | Firebase Admin SDK, credentials... | `.via(PUSH)` |\n| 🔗 | Webhook | Custom HTTP, payload template | `.via(Channel.custom(\"pagerduty\"))` |\n| <img src=\"https://cdn.simpleicons.org/socketdotio\" width=\"16\"> | WebSocket | Java WebSocket API, reconnect logic | `.via(WEBSOCKET)` |\n| <img src=\"https://cdn.simpleicons.org/googlechat\" width=\"16\"> | Google Chat | Webhook HTTP, JSON payload | `.via(GOOGLE_CHAT)` |\n| <img src=\"https://cdn.simpleicons.org/x/white\" width=\"16\"> | Twitter/X | OAuth 1.0a, API v2 setup | `.via(TWITTER)` |\n| 💼 | LinkedIn | OAuth 2.0, REST API setup | `.via(LINKEDIN)` |\n| <img src=\"https://cdn.simpleicons.org/notion/white\" width=\"16\"> | Notion | Integration Token, API setup | `.via(NOTION)` |\n| <img src=\"https://cdn.simpleicons.org/twitch\" width=\"16\"> | Twitch | OAuth 2.0, Twitch API setup | `.via(TWITCH)` |\n| <img src=\"https://cdn.simpleicons.org/youtube\" width=\"16\"> | YouTube | YouTube Data API v3 setup | `.via(YOUTUBE)` |\n| <img src=\"https://cdn.simpleicons.org/instagram\" width=\"16\"> | Instagram | Meta Graph API setup | `.via(INSTAGRAM)` |\n| 📧 | SendGrid | SendGrid API, webhook tracking | `.via(Channel.custom(\"sendgrid\"))` |\n| <img src=\"https://cdn.simpleicons.org/tiktok/EE1D52\" width=\"16\"> | TikTok Shop | HMAC-SHA256, Shop API | `.via(TIKTOK_SHOP)` |\n| <img src=\"https://cdn.simpleicons.org/facebook\" width=\"16\"> | Facebook | Graph API, Page tokens | `.via(FACEBOOK)` |\n| ☁️ | AWS SNS | AWS SDK, credentials, ARN | `.via(Channel.custom(\"aws-sns\"))` |\n| <img src=\"https://cdn.simpleicons.org/mailgun/F06B66\" width=\"16\"> | Mailgun | Mailgun API, domain setup | `.via(Channel.custom(\"mailgun\"))` |\n| <img src=\"https://cdn.simpleicons.org/pagerduty/06AC38\" width=\"16\"> | PagerDuty | Events API v2, routing key | `.via(Channel.custom(\"pagerduty\"))` |\n| <img src=\"https://cdn.simpleicons.org/kick\" width=\"16\"> | Kick | Public API, OAuth 2.1 | `.via(KICK)` |\n| | Multiple channels | Completely different code for each | Same fluent API |\n| Fallback | Manual try/catch chain | `.fallback(SMS)` |\n| Retry | Implement yourself | Built-in exponential backoff |\n| Async | Thread pools, CompletableFuture | `.sendAsync()` |\n| Scheduling | ScheduledExecutor, timer logic | `.schedule(Duration.ofMinutes(30))` |\n| Templates | Each channel has its own engine | One template, all channels |\n| i18n | Manual locale resolution | `.locale(Locale.PT_BR)` |\n| Rate limiting | Token bucket from scratch | Config-driven per-channel |\n| Tracking | Build your own delivery log | Built-in receipts + JPA |\n| Dead letters | Lost in the void | Auto-captured in DLQ |\n| Deduplication | Track sent messages yourself | Built-in content hash / explicit key |\n| Template versions | Manage files manually | `.templateVersion(\"v2\")` + A/B test |\n| Batch | Loop and pray | `.toAll(users).send()` |\n| Monitoring | Wire Micrometer yourself | Auto-configured counters |\n| Health checks | Write an Actuator indicator | Auto-configured |\n| Admin UI | Build your own dashboard | Built-in `/notify-admin` |\n| Circuit breaker | Implement yourself per channel | Built-in per-channel circuit breaker |\n| Orchestration | Manual escalation logic | `.orchestrate().first(EMAIL).ifNoOpen(24h).then(PUSH)` |\n| A/B testing | External service + glue code | Built-in `.abTest(\"exp\").variant(...).split(50,50)` |\n| Testing | Mock everything | `TestNotifyHub` captures all sends |\n| New channel | Build from scratch | Implement one interface |\n\n---\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Features](#features)\n  - [Fallback Chain](#fallback-chain)\n  - [Multi-Channel Send](#multi-channel-send)\n  - [Async Sending](#async-sending)\n  - [Retry with Backoff](#retry-with-backoff)\n  - [Templates (Mustache)](#templates-mustache)\n  - [i18n (Internationalization)](#i18n-internationalization)\n  - [Attachments](#attachments)\n  - [Priority Levels](#priority-levels)\n  - [Rate Limiting](#rate-limiting)\n  - [Dead Letter Queue (DLQ)](#dead-letter-queue-dlq)\n  - [Batch Send](#batch-send)\n  - [Delivery Tracking](#delivery-tracking)\n  - [Scheduled Notifications](#scheduled-notifications)\n  - [Notification Routing](#notification-routing)\n  - [Notifiable Interface](#notifiable-interface)\n  - [Message Deduplication](#message-deduplication)\n  - [Template Versioning](#template-versioning)\n  - [Custom Channels](#custom-channels)\n  - [Event Listeners + Spring Events](#event-listeners--spring-events)\n  - [Named Recipients](#named-recipients)\n  - [Message Queue (RabbitMQ / Kafka)](#message-queue-rabbitmq--kafka)\n  - [Circuit Breaker](#circuit-breaker)\n  - [Bulkhead (Concurrency Isolation)](#bulkhead-concurrency-isolation)\n  - [Multi-Channel Orchestration](#multi-channel-orchestration)\n  - [A/B Testing](#ab-testing)\n  - [Cron Scheduling](#cron-scheduling)\n  - [Quiet Hours](#quiet-hours)\n  - [Testing Utilities](#testing-utilities)\n- [Supported Channels](#supported-channels)\n- [Admin Dashboard](#admin-dashboard)\n- [Spring Boot Integration](#spring-boot-integration)\n- [Configuration Reference](#configuration-reference)\n- [Without Spring Boot](#without-spring-boot)\n- [MCP Server (AI Agents)](#mcp-server-ai-agents)\n- [Running the Demo](#running-the-demo)\n- [Architecture](#architecture)\n- [Maven Central](#maven-central)\n- [Roadmap](#roadmap)\n- [License](#license)\n\n---\n\n## Quick Start\n\n### 1. Add the dependency\n\n```xml\n<dependency>\n    <groupId>io.github.gabrielbbaldez</groupId>\n    <artifactId>notify-spring-boot-starter</artifactId>\n    <version>1.0.0</version>\n</dependency>\n```\n\n> **Need extra channels?** Add optional modules:\n> ```xml\n> <!-- SMS + WhatsApp (Twilio) -->\n> <dependency>\n>     <groupId>io.github.gabrielbbaldez</groupId>\n>     <artifactId>notify-sms</artifactId>\n>     <version>1.0.0</version>\n> </dependency>\n>\n> <!-- Slack / Telegram / Discord / Teams / Firebase Push / Webhook -->\n> <dependency>\n>     <groupId>io.github.gabrielbbaldez</groupId>\n>     <artifactId>notify-slack</artifactId>\n>     <version>1.0.0</version>\n> </dependency>\n>\n> <!-- WebSocket / Google Chat -->\n> <dependency>\n>     <groupId>io.github.gabrielbbaldez</groupId>\n>     <artifactId>notify-websocket</artifactId>\n>     <version>1.0.0</version>\n> </dependency>\n> ```\n\n### 2. Configure in `application.yml`\n\n```yaml\nnotify:\n  channels:\n    email:\n      host: smtp.gmail.com\n      port: 587\n      username: ${GMAIL_USER}\n      password: ${GMAIL_PASS}\n      from: noreply@myapp.com\n      from-name: MyApp\n      tls: true\n  retry:\n    max-attempts: 3\n    strategy: exponential\n  tracking:\n    enabled: true\n```\n\n### 3. Inject and use\n\n```java\n@Service\npublic class OrderService {\n\n    private final NotifyHub notify;\n\n    public OrderService(NotifyHub notify) {\n        this.notify = notify;\n    }\n\n    public void confirmOrder(Order order) {\n        notify.to(order.getCustomer())\n            .via(Channel.EMAIL)\n            .subject(\"Order confirmed!\")\n            .template(\"order-confirmed\")\n            .param(\"customerName\", order.getCustomer().getName())\n            .param(\"orderId\", order.getId())\n            .param(\"total\", order.getTotal())\n            .send();\n    }\n}\n```\n\nThat's it. Three steps.\n\n---\n\n## Features\n\n### Fallback Chain\n\nIf the primary channel fails, automatically try the next one:\n\n```java\nnotify.to(user)\n    .via(Channel.WHATSAPP)\n    .fallback(Channel.SMS)\n    .fallback(Channel.EMAIL)\n    .template(\"payment-reminder\")\n    .param(\"amount\", \"R$ 150,00\")\n    .send();\n// Tries WhatsApp -> SMS -> Email\n```\n\n### Multi-Channel Send\n\nSend through ALL channels simultaneously:\n\n```java\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .via(Channel.SLACK)\n    .via(Channel.TEAMS)\n    .subject(\"Security Alert\")\n    .content(\"Login from a new device detected\")\n    .sendAll();\n```\n\n### Async Sending\n\nSend notifications without blocking:\n\n```java\n// Fire and forget\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .template(\"welcome\")\n    .sendAsync();\n\n// Or wait for result\nCompletableFuture<Void> future = notify.to(user)\n    .via(Channel.EMAIL)\n    .via(Channel.SLACK)\n    .content(\"Deploy complete!\")\n    .sendAllAsync();\n\nfuture.thenRun(() -> log.info(\"All notifications sent!\"));\n```\n\n### Retry with Backoff\n\nAutomatic retry with exponential or fixed backoff:\n\n```yaml\n# application.yml (global)\nnotify:\n  retry:\n    max-attempts: 3\n    strategy: exponential  # waits 1s, 2s, 4s...\n```\n\n```java\n// Or per-notification\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .retry(3)\n    .template(\"invoice\")\n    .send();\n```\n\n### Templates (Mustache)\n\nCreate templates in `src/main/resources/templates/notify/`:\n\n**order-confirmed.html** (auto-used for email):\n```html\n<h1>Hello, {{customerName}}!</h1>\n<p>Your order <strong>#{{orderId}}</strong> has been confirmed.</p>\n<p>Total: <strong>{{total}}</strong></p>\n```\n\n**order-confirmed.txt** (auto-used for SMS/WhatsApp/Slack/Telegram/Discord/Teams):\n```\nHello {{customerName}}, your order #{{orderId}} is confirmed. Total: {{total}}\n```\n\nThe library picks `.html` for email and `.txt` for other channels automatically.\n\n### i18n (Internationalization)\n\nTemplates support locale-based resolution with automatic fallback:\n\n```java\n// User with locale\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .locale(Locale.forLanguageTag(\"pt-BR\"))\n    .template(\"welcome\")\n    .param(\"name\", user.getName())\n    .send();\n```\n\nTemplate resolution order: `welcome_pt_BR.html` -> `welcome_pt.html` -> `welcome.html`\n\nYour `Notifiable` can also return a locale:\n\n```java\npublic class User implements Notifiable {\n    @Override\n    public Locale getLocale() {\n        return Locale.forLanguageTag(\"pt-BR\");\n    }\n}\n```\n\n### Attachments\n\nAttach files to email notifications:\n\n```java\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .subject(\"Your Invoice\")\n    .template(\"invoice\")\n    .attach(\"invoice.pdf\", pdfBytes, \"application/pdf\")\n    .attach(new File(\"/reports/monthly.xlsx\"))\n    .attach(Attachment.fromFile(contractFile))\n    .send();\n```\n\n### Priority Levels\n\nSet notification priority. **URGENT** notifications bypass rate limiting:\n\n```java\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .priority(Priority.URGENT)\n    .subject(\"SERVER DOWN!\")\n    .content(\"Production server is unresponsive\")\n    .send();\n```\n\nAvailable priorities: `URGENT` (bypasses rate limits), `HIGH`, `NORMAL` (default), `LOW`.\n\n### Rate Limiting\n\nControl notification throughput per-channel:\n\n```yaml\nnotify:\n  rate-limit:\n    enabled: true\n    max-requests: 100\n    window: 1m\n    channels:\n      email:\n        max-requests: 50\n        window: 1m\n      sms:\n        max-requests: 10\n        window: 1m\n```\n\nRate limiting uses a token bucket algorithm. URGENT priority notifications always bypass rate limits.\n\n### Dead Letter Queue (DLQ)\n\nFailed notifications (after all retries) are automatically captured in the DLQ:\n\n```yaml\nnotify:\n  tracking:\n    enabled: true\n    dlq-enabled: true\n```\n\nView and manage dead letters via the admin dashboard at `/notify-admin/dlq`, or programmatically:\n\n```java\nDeadLetterQueue dlq = hub.getDeadLetterQueue();\nList<DeadLetter> failed = dlq.findAll();\ndlq.remove(deadLetterId); // after manual reprocessing\n```\n\n### Batch Send\n\nSend notifications to multiple recipients at once:\n\n```java\n// By email addresses\nnotify.toAll(List.of(\"user1@test.com\", \"user2@test.com\", \"user3@test.com\"))\n    .via(Channel.EMAIL)\n    .subject(\"System Maintenance\")\n    .template(\"maintenance-notice\")\n    .param(\"date\", \"2025-03-01\")\n    .send();\n\n// By Notifiable entities\nnotify.toAllNotifiable(users)\n    .via(Channel.EMAIL)\n    .template(\"newsletter\")\n    .send();\n\n// Async batch\nnotify.toAll(recipients)\n    .via(Channel.EMAIL)\n    .template(\"promo\")\n    .sendAsync();\n```\n\n### Delivery Tracking\n\nTrack every notification with delivery receipts:\n\n```yaml\nnotify:\n  tracking:\n    enabled: true\n    type: memory  # or \"jpa\" for database persistence\n```\n\n```java\n// Send and get a receipt\nDeliveryReceipt receipt = notify.to(user)\n    .via(Channel.EMAIL)\n    .content(\"Hello!\")\n    .sendTracked();\n\nSystem.out.println(receipt.getStatus());    // SENT\nSystem.out.println(receipt.getId());         // uuid\nSystem.out.println(receipt.getTimestamp());  // 2025-01-15T10:30:00Z\n```\n\nFor database persistence, add the JPA tracker module:\n\n```xml\n<dependency>\n    <groupId>io.github.gabrielbbaldez</groupId>\n    <artifactId>notify-tracker-jpa</artifactId>\n    <version>1.0.0</version>\n</dependency>\n```\n\n```yaml\nnotify:\n  tracking:\n    enabled: true\n    type: jpa\n```\n\n### Scheduled Notifications\n\nSchedule notifications for future delivery:\n\n```java\nScheduledNotification scheduled = notify.to(user)\n    .via(Channel.EMAIL)\n    .subject(\"Reminder\")\n    .content(\"Don't forget your appointment tomorrow!\")\n    .schedule(Duration.ofHours(24));\n\n// Check status\nscheduled.getStatus();        // SCHEDULED, SENT, FAILED, CANCELLED\nscheduled.getRemainingDelay(); // PT23H59M...\n\n// Cancel if needed\nscheduled.cancel();\n```\n\n### Notification Routing\n\nAuto-route notifications based on user preferences:\n\n```java\npublic class User implements Notifiable {\n    @Override\n    public List<Channel> getPreferredChannels() {\n        return List.of(Channel.WHATSAPP, Channel.SMS, Channel.EMAIL);\n    }\n}\n\n// Auto-routes: WhatsApp (primary) -> SMS (fallback) -> Email (fallback)\nnotify.notify(user)\n    .template(\"order-update\")\n    .param(\"orderId\", \"12345\")\n    .send();\n```\n\nConditional routing with rules:\n\n```java\nNotificationRouter router = NotificationRouter.builder()\n    .rule(RoutingRule.timeBasedRule(\n        LocalTime.of(9, 0), LocalTime.of(18, 0),\n        Channel.SLACK, Channel.EMAIL))  // Slack during business hours, email after\n    .build();\n```\n\n### Notifiable Interface\n\nMake your User entity a notification recipient:\n\n```java\n@Entity\npublic class User implements Notifiable {\n\n    private String name;\n    private String email;\n    private String phone;\n\n    @Override\n    public String getNotifyEmail() { return email; }\n\n    @Override\n    public String getNotifyPhone() { return phone; }\n\n    @Override\n    public String getNotifyName() { return name; }\n\n    @Override\n    public Locale getLocale() { return Locale.forLanguageTag(\"pt-BR\"); }\n\n    @Override\n    public List<Channel> getPreferredChannels() {\n        return List.of(Channel.EMAIL, Channel.SMS);\n    }\n}\n```\n\nThen just pass the user object:\n\n```java\nnotify.to(user)       // resolves email/phone automatically\n    .via(Channel.EMAIL)\n    .template(\"welcome\")\n    .send();\n```\n\nOr use raw addresses:\n\n```java\nnotify.to(\"user@email.com\").via(Channel.EMAIL).content(\"Hello!\").send();\nnotify.toPhone(\"+5511999999999\").via(Channel.SMS).content(\"Code: 1234\").send();\n```\n\n### Message Deduplication\n\nPrevent duplicate notifications automatically with content hashing or explicit keys:\n\n```yaml\nnotify:\n  deduplication:\n    enabled: true\n    ttl: 24h\n    strategy: content-hash  # content-hash | explicit-key | both\n```\n\n```java\n// Auto-dedup by content hash (same recipient + channel + content = skipped)\nnotify.to(user).via(EMAIL).content(\"Order confirmed\").send();\nnotify.to(user).via(EMAIL).content(\"Order confirmed\").send(); // skipped!\n\n// Dedup by explicit key\nnotify.to(user).via(EMAIL)\n    .deduplicationKey(\"order-\" + orderId)\n    .template(\"order-confirmed\")\n    .send();\n```\n\nStrategies:\n- **`content-hash`** — SHA-256 hash of recipient + channel + subject + content\n- **`explicit-key`** — uses the key provided via `.deduplicationKey(\"...\")`\n- **`both`** — uses explicit key if provided, otherwise falls back to content hash\n\nWithout Spring Boot:\n\n```java\nNotifyHub notify = NotifyHub.builder()\n    .deduplicationStore(new InMemoryDeduplicationStore(Duration.ofHours(12)))\n    .channel(emailChannel)\n    .build();\n```\n\n### Template Versioning\n\nManage multiple versions of templates for A/B testing or gradual rollouts:\n\n```\ntemplates/notify/\n├── order-confirmed.html           ← default version\n├── order-confirmed@v1.html        ← version v1\n├── order-confirmed@v2.html        ← version v2\n├── order-confirmed_pt_BR@v2.html  ← v2 with i18n\n└── order-confirmed.txt            ← text default\n```\n\n```java\n// Use a specific version\nnotify.to(user).via(EMAIL)\n    .template(\"order-confirmed\")\n    .templateVersion(\"v2\")\n    .param(\"orderId\", \"123\")\n    .send();\n\n// No version = default template (backward compatible)\nnotify.to(user).via(EMAIL)\n    .template(\"order-confirmed\")\n    .send();\n\n// A/B testing\nString version = abTestService.getVariant(user, \"email-template\");\nnotify.to(user).via(EMAIL)\n    .template(\"welcome\")\n    .templateVersion(version)  // \"v1\" or \"v2\"\n    .send();\n```\n\nResolution order: `{name}@{version}_{locale}.{variant}` → `{name}@{version}.{variant}` → `{name}_{locale}.{variant}` → `{name}.{variant}`\n\n### Custom Channels\n\nCreate your own channel by implementing one interface:\n\n```java\n@Component\npublic class PushChannel implements NotificationChannel {\n\n    @Override\n    public String getName() { return \"push\"; }\n\n    @Override\n    public void send(Notification notification) {\n        firebaseClient.send(notification.getRecipient(), notification.getRenderedContent());\n    }\n\n    @Override\n    public boolean isAvailable() { return true; }\n}\n```\n\nUse it:\n\n```java\nnotify.to(user)\n    .via(Channel.custom(\"push\"))\n    .template(\"new-message\")\n    .send();\n```\n\nSpring Boot auto-discovers any `NotificationChannel` bean. No extra config needed.\n\n### Event Listeners + Spring Events\n\nMonitor notification outcomes with the listener interface:\n\n```java\n@Component\npublic class NotifyMonitor implements NotificationListener {\n\n    @Override\n    public void onSuccess(String channel, String template) {\n        metrics.increment(\"notifications.sent.\" + channel);\n    }\n\n    @Override\n    public void onFailure(String channel, String template, Exception error) {\n        log.error(\"Failed on {}: {}\", channel, error.getMessage());\n        alertService.warn(\"Channel \" + channel + \" is failing\");\n    }\n\n    @Override\n    public void onScheduled(String channel, String recipient, Duration delay) {\n        log.info(\"Scheduled for {} in {}\", recipient, delay);\n    }\n}\n```\n\nOr use **Spring Application Events** (auto-configured):\n\n```java\n@Component\npublic class NotificationEventHandler {\n\n    @EventListener\n    public void onSent(NotificationSentEvent event) {\n        log.info(\"Sent via {} to {}\", event.getChannel(), event.getRecipient());\n    }\n\n    @EventListener\n    public void onFailed(NotificationFailedEvent event) {\n        log.error(\"Failed: {}\", event.getError().getMessage());\n    }\n}\n```\n\n### Named Recipients\n\nSend notifications to multiple destinations per channel using named aliases. Instead of one hardcoded webhook URL or chat ID, configure as many as you need:\n\n**Configure in `application.yml`:**\n\n```yaml\nnotify:\n  channels:\n    discord:\n      webhook-url: ${DISCORD_DEFAULT}      # default destination\n      username: NotifyHub\n      avatar-url: https://example.com/logo.png\n      recipients:\n        alerts: https://discord.com/api/webhooks/111/aaa\n        devops: https://discord.com/api/webhooks/222/bbb\n        general: https://discord.com/api/webhooks/333/ccc\n\n    slack:\n      webhook-url: ${SLACK_DEFAULT}\n      recipients:\n        engineering: https://hooks.slack.com/services/XXX/YYY/ZZZ\n        marketing: https://hooks.slack.com/services/AAA/BBB/CCC\n\n    telegram:\n      bot-token: ${TELEGRAM_BOT_TOKEN}\n      chat-id: ${TELEGRAM_DEFAULT_CHAT}\n      recipients:\n        alerts: \"-1001234567890\"\n        devops: \"-1009876543210\"\n```\n\n**Use with the Java API:**\n\n```java\n// Send to a named alias\nnotify.to(\"alerts\").via(DISCORD).content(\"Server is down!\").send();\nnotify.to(\"engineering\").via(SLACK).content(\"Deploy complete\").send();\nnotify.to(\"devops\").via(TELEGRAM).content(\"CPU at 95%\").send();\n\n// Send to default (no alias)\nnotify.to(\"user\").via(DISCORD).content(\"Hello!\").send();\n\n// Pass a raw URL directly (no alias needed)\nnotify.to(\"https://discord.com/api/webhooks/444/ddd\").via(DISCORD).content(\"Direct!\").send();\n```\n\n**Use with the MCP Server (AI Agents):**\n\n```\nsend_discord(recipient=\"alerts\", body=\"Server is down!\")\nsend_slack(recipient=\"engineering\", body=\"Deploy complete\")\nsend_telegram(recipient=\"devops\", body=\"CPU at 95%\")\n```\n\n**Environment variables for MCP/Docker:**\n\n```bash\n# Default webhook\nNOTIFY_CHANNELS_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/111/aaa\n\n# Named recipients (RECIPIENTS_<NAME>)\nNOTIFY_CHANNELS_DISCORD_RECIPIENTS_ALERTS=https://discord.com/api/webhooks/222/bbb\nNOTIFY_CHANNELS_DISCORD_RECIPIENTS_DEVOPS=https://discord.com/api/webhooks/333/ccc\n\n# Same pattern for all channels\nNOTIFY_CHANNELS_SLACK_RECIPIENTS_ENGINEERING=https://hooks.slack.com/services/XXX\nNOTIFY_CHANNELS_TELEGRAM_RECIPIENTS_ALERTS=-1001234567890\nNOTIFY_CHANNELS_TEAMS_RECIPIENTS_GENERAL=https://outlook.office.com/webhook/XXX\nNOTIFY_CHANNELS_GOOGLE_CHAT_RECIPIENTS_TEAM=https://chat.googleapis.com/v1/spaces/XXX\n```\n\n**Resolution order:** alias match in recipients map > raw URL/value passthrough > default from config.\n\nSupported on: **Discord, Slack, Telegram, Teams, Google Chat**.\n\n### Message Queue (RabbitMQ / Kafka)\n\nDecouple notification sending with async message queues. NotifyHub provides two modules:\n\n#### RabbitMQ\n\n```xml\n<dependency>\n    <groupId>io.github.gabrielbbaldez</groupId>\n    <artifactId>notify-queue-rabbitmq</artifactId>\n    <version>1.0.0</version>\n</dependency>\n```\n\n```yaml\nspring.rabbitmq.host: localhost\nspring.rabbitmq.port: 5672\n\nnotify.queue.rabbitmq:\n  enabled: true\n  queue-name: notifyhub-notifications\n  exchange-name: notifyhub-exchange\n  routing-key: notification\n  consumer:\n    enabled: true\n    concurrency: 1\n    max-concurrency: 5\n```\n\n```java\n@Autowired RabbitNotificationProducer producer;\n\n// Enqueue for async delivery\nproducer.enqueue(QueuedNotification.builder()\n    .recipient(\"user@example.com\")\n    .channelName(\"email\")\n    .subject(\"Welcome!\")\n    .templateName(\"welcome\")\n    .params(Map.of(\"name\", \"Gabriel\"))\n    .build());\n// Consumer picks it up and sends via NotifyHub automatically\n```\n\n#### Apache Kafka\n\n```xml\n<dependency>\n    <groupId>io.github.gabrielbbaldez</groupId>\n    <artifactId>notify-queue-kafka</artifactId>\n    <version>1.0.0</version>\n</dependency>\n```\n\n```yaml\nspring.kafka.bootstrap-servers: localhost:9092\n\nnotify.queue.kafka:\n  enabled: true\n  topic: notifyhub-notifications\n  consumer:\n    enabled: true\n    group-id: notifyhub-group\n    concurrency: 1\n```\n\n```java\n@Autowired KafkaNotificationProducer producer;\n\n// Same API as RabbitMQ — just different transport\nproducer.enqueue(QueuedNotification.builder()\n    .recipient(\"+5548999999999\")\n    .channelName(\"sms\")\n    .rawContent(\"Your code is 1234\")\n    .priority(\"URGENT\")\n    .build());\n```\n\nBoth modules support: templates, priority, deduplication keys, delivery tracking, and phone number routing for SMS/WhatsApp.\n\n### Circuit Breaker\n\nPer-channel circuit breaker prevents cascading failures. If a channel fails repeatedly, the circuit opens and short-circuits further attempts:\n\n```java\n// Without Spring Boot\nNotifyHub notify = NotifyHub.builder()\n    .channel(emailChannel)\n    .circuitBreaker(CircuitBreakerConfig.defaults()) // 5 failures → open for 30s\n    .build();\n\n// Custom thresholds\nNotifyHub notify = NotifyHub.builder()\n    .channel(emailChannel)\n    .circuitBreaker(CircuitBreakerConfig.custom()\n        .failureThreshold(3)\n        .openDuration(Duration.ofMinutes(1))\n        .windowSize(Duration.ofSeconds(30))\n        .build())\n    .build();\n```\n\n```yaml\n# Spring Boot\nnotify:\n  circuit-breaker:\n    enabled: true\n    failure-threshold: 5\n    open-duration: 30s\n    window-size: 60s\n```\n\nStates: **CLOSED** (normal) → **OPEN** (rejecting) → **HALF_OPEN** (testing recovery). The health endpoint includes circuit breaker status per channel.\n\n### Bulkhead (Concurrency Isolation)\n\nLimit concurrent sends per channel to prevent resource exhaustion:\n\n```java\nNotifyHub notify = NotifyHub.builder()\n    .channel(emailChannel)\n    .bulkhead(BulkheadConfig.defaults())       // 10 concurrent per channel\n    .bulkhead(BulkheadConfig.perChannel(5))    // or custom limit\n    .build();\n```\n\n### Multi-Channel Orchestration\n\nBuild escalation workflows that promote through channels if the user doesn't engage:\n\n```java\nnotify.to(user)\n    .orchestrate()\n    .first(Channel.EMAIL)\n        .template(\"order-update\")\n    .ifNoOpen(Duration.ofHours(24))\n    .then(Channel.PUSH)\n        .content(\"You have an unread order update\")\n    .ifNoOpen(Duration.ofHours(48))\n    .then(Channel.SMS)\n        .content(\"Order update waiting — check your email\")\n    .execute();\n```\n\nEach step waits for the specified duration before escalating to the next channel.\n\n### A/B Testing\n\nBuilt-in deterministic A/B testing for notifications. Variant assignment is hash-based (SHA-256) — the same recipient always gets the same variant:\n\n```java\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .subject(\"Welcome!\")\n    .abTest(\"welcome-experiment\")\n        .variant(\"control\", b -> b.template(\"welcome-v1\"))\n        .variant(\"new-design\", b -> b.template(\"welcome-v2\"))\n        .split(50, 50);\n```\n\nSupports any number of variants with weighted splits. Deterministic hashing ensures consistent experiences across sends.\n\n### Cron Scheduling\n\nSchedule recurring notifications with cron expressions:\n\n```java\nScheduledNotification job = notify.to(user)\n    .via(Channel.EMAIL)\n    .template(\"weekly-digest\")\n    .cron(\"0 9 * * MON\"); // Every Monday at 9 AM\n```\n\nSupports standard 5-field cron syntax: minute, hour, day-of-month, month, day-of-week. Includes ranges, lists, steps, and named days/months.\n\n### Quiet Hours\n\nRespect user preferences for notification timing:\n\n```java\npublic class User implements Notifiable {\n    @Override\n    public QuietHours getQuietHours() {\n        return QuietHours.between(\n            LocalTime.of(22, 0),  // 10 PM\n            LocalTime.of(8, 0),   // 8 AM\n            ZoneId.of(\"America/Sao_Paulo\")\n        );\n    }\n\n    @Override\n    public Set<Channel> getOptedOutChannels() {\n        return Set.of(Channel.SMS); // User opted out of SMS\n    }\n}\n```\n\nNotifications sent during quiet hours are delayed to the next allowed window. Opted-out channels are silently skipped.\n\n### Testing Utilities\n\n`TestNotifyHub` provides a test-friendly wrapper with capturing channels for all built-in channel types:\n\n```java\n@Test\nvoid shouldSendWelcomeEmail() {\n    TestNotifyHub test = TestNotifyHub.create();\n\n    test.to(\"user@test.com\")\n        .via(Channel.EMAIL)\n        .subject(\"Welcome\")\n        .content(\"Hello!\")\n        .send();\n\n    assertThat(test.sent()).hasSize(1);\n    assertThat(test.sent(\"email\").get(0).subject()).isEqualTo(\"Welcome\");\n}\n```\n\nNo mocking needed — `TestNotifyHub` captures all notifications in memory for assertions. Call `test.reset()` between tests.\n\n---\n\n## Supported Channels\n\n| | Channel | Provider | Module |\n|:-:|---------|----------|--------|\n| <img src=\"https://cdn.simpleicons.org/gmail\" width=\"18\"> | **Email** | SMTP (Gmail, SES, Outlook, any) | `notify-email` |\n| 📱 | **SMS** | Twilio | `notify-sms` |\n| <img src=\"https://cdn.simpleicons.org/whatsapp\" width=\"18\"> | **WhatsApp** | Twilio | `notify-sms` |\n| <img src=\"https://cdn.simpleicons.org/slack\" width=\"18\"> | **Slack** | Incoming Webhooks | `notify-slack` |\n| <img src=\"https://cdn.simpleicons.org/telegram\" width=\"18\"> | **Telegram** | Bot API | `notify-telegram` |\n| <img src=\"https://cdn.simpleicons.org/discord\" width=\"18\"> | **Discord** | Webhooks | `notify-discord` |\n| 👥 | **Microsoft Teams** | Incoming Webhooks | `notify-teams` |\n| <img src=\"https://cdn.simpleicons.org/firebase\" width=\"18\"> | **Push (FCM)** | Firebase Cloud Messaging | `notify-push-firebase` |\n| 🔗 | **Webhook** | Any HTTP endpoint | `notify-webhook` |\n| <img src=\"https://cdn.simpleicons.org/socketdotio\" width=\"18\"> | **WebSocket** | JDK WebSocket (`java.net.http`) | `notify-websocket` |\n| <img src=\"https://cdn.simpleicons.org/googlechat\" width=\"18\"> | **Google Chat** | Webhooks | `notify-google-chat` |\n| <img src=\"https://cdn.simpleicons.org/x/white\" width=\"18\"> | **Twitter/X** | API v2 (OAuth 1.0a) | `notify-twitter` |\n| 💼 | **LinkedIn** | REST API (OAuth 2.0) | `notify-linkedin` |\n| <img src=\"https://cdn.simpleicons.org/notion/white\" width=\"18\"> | **Notion** | API (Integration Token) | `notify-notion` |\n| <img src=\"https://cdn.simpleicons.org/twitch\" width=\"18\"> | **Twitch** | Helix API (OAuth 2.0 auto-refresh) | `notify-twitch` |\n| <img src=\"https://cdn.simpleicons.org/youtube\" width=\"18\"> | **YouTube** | Data API v3 (OAuth auto-refresh) | `notify-youtube` |\n| <img src=\"https://cdn.simpleicons.org/instagram\" width=\"18\"> | **Instagram** | Meta Graph API | `notify-instagram` |\n| 📧 | **SendGrid** | SendGrid API (delivery tracking) | `notify-sendgrid` |\n| <img src=\"https://cdn.simpleicons.org/tiktok/EE1D52\" width=\"18\"> | **TikTok Shop** | TikTok Shop API (HMAC-SHA256) | `notify-tiktok-shop` |\n| <img src=\"https://cdn.simpleicons.org/facebook\" width=\"18\"> | **Facebook** | Graph API (Page + Messenger) | `notify-facebook` |\n| <img src=\"https://cdn.simpleicons.org/whatsapp\" width=\"18\"> | **WhatsApp Cloud** | Meta Cloud API (direct, no Twilio) | `notify-whatsapp` |\n| ☁️ | **AWS SNS** | AWS SDK v2 | `notify-aws-sns` |\n| <img src=\"https://cdn.simpleicons.org/mailgun/F06B66\" width=\"18\"> | **Mailgun** | Mailgun REST API | `notify-mailgun` |\n| <img src=\"https://cdn.simpleicons.org/pagerduty/06AC38\" width=\"18\"> | **PagerDuty** | Events API v2 | `notify-pagerduty` |\n| <img src=\"https://cdn.simpleicons.org/kick\" width=\"18\"> | **Kick** | Public API | `notify-kick` |\n| ➕ | **Custom** | Any — implement one interface | `notify-core` |\n\n---\n\n## Admin Dashboard\n\nNotifyHub includes a built-in admin dashboard for monitoring your notification system.\n\n```yaml\nnotify:\n  admin:\n    enabled: true\n```\n\n```xml\n<dependency>\n    <groupId>io.github.gabrielbbaldez</groupId>\n    <artifactId>notify-admin</artifactId>\n    <version>1.0.0</version>\n</dependency>\n```\n\nAccess at **`/notify-admin`** to see:\n\n- **Dashboard** — metric cards (sent/failed/pending/DLQ/channels/contacts), recent activity feed, system status grid, registered channels\n- **Analytics** — Chart.js charts with send volume, channel distribution, success rate, hourly heatmap\n- **Tracking** — delivery receipts with channel filter and status badges\n- **Dead Letter Queue** — failed notifications with error details and remove action\n- **Channels** — status of each registered channel with health indicators\n- **Audit Log** — complete history of all notification events with event type filter\n- **Audiences** — manage contacts and audience segments with tag-based filtering\n- **Status Webhook** — real-time HTTP callback configuration and delivery history\n\nDark/light theme toggle included — persists across pages via `localStorage`.\n\n---\n\n## Spring Boot Integration\n\n### Micrometer Metrics\n\nAuto-configured when Micrometer is on the classpath:\n\n```xml\n<dependency>\n    <groupId>io.micrometer</groupId>\n    <artifactId>micrometer-core</artifactId>\n</dependency>\n```\n\nExposes counters and gauges via the unified EventBus:\n- `notifyhub.notifications.sent` (tags: channel)\n- `notifyhub.notifications.failed` (tags: channel)\n- `notifyhub.notifications.retried` (tags: channel)\n- `notifyhub.notifications.rate_limited` (tags: channel)\n- `notifyhub.notifications.deduped` (tags: channel)\n- `notifyhub.notifications.circuit_opened` (tags: channel)\n- `notifyhub.notifications.circuit_closed` (tags: channel)\n- `notifyhub.notifications.send_duration` (timer, tags: channel)\n\n### Actuator Health Check\n\nAuto-configured when Spring Boot Actuator is on the classpath:\n\n```\nGET /actuator/health/notifyhub\n```\n\n```json\n{\n  \"status\": \"UP\",\n  \"details\": {\n    \"email\": { \"status\": \"UP\", \"circuitBreaker\": \"CLOSED\" },\n    \"slack\": { \"status\": \"UP\", \"circuitBreaker\": \"CLOSED\" },\n    \"totalChannels\": 2,\n    \"availableChannels\": 2\n  }\n}\n```\n\nStatus: **UP** (all channels available), **DEGRADED** (some down), **DOWN** (all down). When circuit breaker is configured, each channel also reports its circuit state.\n\n### Actuator Info\n\n```\nGET /actuator/info\n```\n\n```json\n{\n  \"notifyhub\": {\n    \"version\": \"1.0.0\",\n    \"channels\": [\"email\", \"slack\", \"teams\"],\n    \"tracking.enabled\": true,\n    \"dlq.enabled\": true\n  }\n}\n```\n\n### OpenTelemetry Tracing\n\nAuto-configured when Micrometer Observation and an OTel bridge are on the classpath.\nAdd these dependencies to export distributed traces via OTLP:\n\n```xml\n<dependency>\n    <groupId>io.micrometer</groupId>\n    <artifactId>micrometer-tracing-bridge-otel</artifactId>\n</dependency>\n<dependency>\n    <groupId>io.opentelemetry</groupId>\n    <artifactId>opentelemetry-exporter-otlp</artifactId>\n</dependency>\n```\n\nConfigure the OTLP endpoint in `application.yml`:\n\n```yaml\nmanagement:\n  tracing:\n    sampling:\n      probability: 1.0       # 100% sampling (adjust for production)\n  otlp:\n    tracing:\n      endpoint: http://localhost:4318/v1/traces\n```\n\nCreates observations (spans):\n- `notifyhub.send` (tags: channel, template, outcome)\n- `notifyhub.schedule` (tags: channel, outcome)\n\nCompatible with Jaeger, Zipkin, Grafana Tempo, Datadog, and any OTLP-compatible collector.\n\n### Webhook HMAC Signing\n\nThe 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.\n\n```yaml\nnotify:\n  status-webhook:\n    url: https://your-server.com/webhook\n    signing-secret: ${WEBHOOK_SECRET}   # any secret string\n```\n\nEach request includes the header:\n```\nX-NotifyHub-Signature: sha256=<hex-encoded HMAC-SHA256 of request body>\n```\n\n**Verify in your server:**\n```java\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(secret.getBytes(), \"HmacSHA256\"));\nString expected = \"sha256=\" + HexFormat.of().formatHex(mac.doFinal(body.getBytes()));\nboolean valid = MessageDigest.isEqual(expected.getBytes(), signature.getBytes());\n```\n\n---\n\n## Configuration Reference\n\nFull `application.yml` with all options:\n\n```yaml\nnotify:\n  channels:\n    email:\n      host: smtp.gmail.com\n      port: 587\n      username: ${GMAIL_USER}\n      password: ${GMAIL_PASS}\n      from: noreply@myapp.com\n      from-name: MyApp\n      tls: true\n      ssl: false\n\n    sms:\n      account-sid: ${TWILIO_SID}\n      auth-token: ${TWILIO_TOKEN}\n      from-number: \"+1234567890\"\n\n    whatsapp:\n      account-sid: ${TWILIO_SID}\n      auth-token: ${TWILIO_TOKEN}\n      from-number: \"+14155238886\"\n\n    slack:\n      webhook-url: ${SLACK_WEBHOOK}\n      recipients:                          # named aliases (optional)\n        engineering: https://hooks.slack.com/services/XXX\n        marketing: https://hooks.slack.com/services/YYY\n\n    telegram:\n      bot-token: ${TELEGRAM_BOT_TOKEN}\n      chat-id: ${TELEGRAM_CHAT_ID}\n      recipients:                          # named aliases (optional)\n        alerts: \"-1001234567890\"\n        devops: \"-1009876543210\"\n\n    discord:\n      webhook-url: ${DISCORD_WEBHOOK}\n      username: NotifyHub\n      avatar-url: https://example.com/logo.png\n      recipients:                          # named aliases (optional)\n        alerts: https://discord.com/api/webhooks/111/aaa\n        devops: https://discord.com/api/webhooks/222/bbb\n\n    teams:\n      webhook-url: ${TEAMS_WEBHOOK}\n      recipients:                          # named aliases (optional)\n        general: https://outlook.office.com/webhook/XXX\n\n    push:\n      credentials-path: ${FIREBASE_CREDENTIALS}\n      project-id: ${FIREBASE_PROJECT_ID}\n\n    webhooks:\n      - name: pagerduty\n        url: https://events.pagerduty.com/v2/enqueue\n        headers:\n          Authorization: \"Token ${PAGERDUTY_TOKEN}\"\n        payload-template: '{\"summary\":\"{{content}}\"}'\n\n    websocket:\n      uri: wss://echo.example.com/ws\n      timeout-ms: 10000\n      reconnect-enabled: true\n      reconnect-delay-ms: 5000\n      max-reconnect-attempts: 3\n      headers:\n        Authorization: \"Bearer ${WS_TOKEN}\"\n      message-format: '{\"type\":\"notification\",\"content\":\"{{content}}\"}'\n\n    google-chat:\n      webhook-url: ${GOOGLE_CHAT_WEBHOOK}\n      timeout-ms: 10000\n      recipients:                          # named aliases (optional)\n        team-a: https://chat.googleapis.com/v1/spaces/XXX/messages?key=YYY\n        team-b: https://chat.googleapis.com/v1/spaces/ZZZ/messages?key=WWW\n\n  retry:\n    max-attempts: 3\n    strategy: exponential\n\n  rate-limit:\n    enabled: true\n    max-requests: 100\n    window: 1m\n    channels:\n      email:\n        max-requests: 50\n        window: 1m\n\n  tracking:\n    enabled: true\n    type: memory         # memory | jpa\n    dlq-enabled: true\n\n  deduplication:\n    enabled: true\n    ttl: 24h\n    strategy: content-hash  # content-hash | explicit-key | both\n\n  admin:\n    enabled: true\n```\n\n---\n\n## Without Spring Boot\n\nNotifyHub works without Spring — use the builder directly:\n\n```java\nNotifyHub notify = NotifyHub.builder()\n    .templateEngine(new MustacheTemplateEngine())\n    .channel(new SmtpEmailChannel(\n        SmtpConfig.builder()\n            .host(\"smtp.gmail.com\").port(587)\n            .username(\"user@gmail.com\").password(\"app-password\")\n            .from(\"noreply@myapp.com\").tls(true)\n            .build()\n    ))\n    .channel(new SlackChannel(\n        SlackConfig.builder()\n            .webhookUrl(\"https://hooks.slack.com/services/XXX/YYY/ZZZ\")\n            .recipients(Map.of(\"engineering\", \"https://hooks.slack.com/services/AAA/BBB/CCC\"))\n            .build()\n    ))\n    .channel(new TeamsChannel(\n        TeamsConfig.builder()\n            .webhookUrl(\"https://outlook.office.com/webhook/XXX/YYY/ZZZ\")\n            .build()\n    ))\n    .channel(new WebhookChannel(\n        WebhookConfig.builder()\n            .name(\"pagerduty\")\n            .url(\"https://events.pagerduty.com/v2/enqueue\")\n            .payloadTemplate(\"{\\\"summary\\\":\\\"{{content}}\\\"}\")\n            .build()\n    ))\n    .channel(new WebSocketChannel(\n        WebSocketConfig.builder()\n            .uri(\"wss://echo.example.com/ws\")\n            .messageFormat(\"{\\\"text\\\":\\\"{{content}}\\\"}\")\n            .build()\n    ))\n    .channel(new GoogleChatChannel(\n        GoogleChatConfig.builder()\n            .webhookUrl(\"https://chat.googleapis.com/v1/spaces/XXX/messages?key=YYY\")\n            .build()\n    ))\n    .deduplicationStore(new InMemoryDeduplicationStore(Duration.ofHours(24)))\n    .defaultRetryPolicy(RetryPolicy.exponential(3))\n    .rateLimiter(new TokenBucketRateLimiter(\n        RateLimitConfig.perMinute(100)))\n    .deadLetterQueue(new InMemoryDeadLetterQueue())\n    .tracker(new InMemoryNotificationTracker())\n    .circuitBreaker(CircuitBreakerConfig.defaults())\n    .bulkhead(BulkheadConfig.defaults())\n    .build();\n\n// Sync\nnotify.to(\"user@email.com\")\n    .via(Channel.EMAIL)\n    .subject(\"Hello!\")\n    .content(\"Welcome to the app!\")\n    .send();\n\n// Async\nnotify.to(\"#general\")\n    .via(Channel.SLACK)\n    .content(\"Deploy complete!\")\n    .sendAsync();\n\n// Tracked\nDeliveryReceipt receipt = notify.to(user)\n    .via(Channel.EMAIL)\n    .content(\"Invoice attached\")\n    .sendTracked();\n\n// Scheduled\nnotify.to(user)\n    .via(Channel.EMAIL)\n    .content(\"Reminder!\")\n    .schedule(Duration.ofMinutes(30));\n\n// Batch\nnotify.toAll(List.of(\"a@test.com\", \"b@test.com\"))\n    .via(Channel.EMAIL)\n    .template(\"announcement\")\n    .send();\n```\n\nOnly `notify-core` + channel modules needed. No Spring dependency.\n\n---\n\n## MCP Server (AI Agents)\n\nNotifyHub 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.\n\n### How it works\n\nThe `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.\n\n### Setup\n\n**1. Build the MCP server:**\n\n```bash\nmvn clean package -pl notify-mcp -am -DskipTests\n```\n\n**2. Configure in Claude Desktop** (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"notify-hub\": {\n      \"command\": \"java\",\n      \"args\": [\"-jar\", \"path/to/notify-mcp-1.0.0.jar\"],\n      \"env\": {\n        \"NOTIFY_CHANNELS_EMAIL_HOST\": \"smtp.gmail.com\",\n        \"NOTIFY_CHANNELS_EMAIL_PORT\": \"587\",\n        \"NOTIFY_CHANNELS_EMAIL_USERNAME\": \"you@gmail.com\",\n        \"NOTIFY_CHANNELS_EMAIL_PASSWORD\": \"app-password\",\n        \"NOTIFY_CHANNELS_SLACK_WEBHOOK_URL\": \"https://hooks.slack.com/...\",\n        \"NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL\": \"https://discord.com/api/webhooks/...\"\n      }\n    }\n  }\n}\n```\n\n**Or for Claude Code** (`.mcp.json` in project root):\n\n```json\n{\n  \"mcpServers\": {\n    \"notify-hub\": {\n      \"command\": \"java\",\n      \"args\": [\"-jar\", \"path/to/notify-mcp-1.0.0.jar\"],\n      \"env\": {\n        \"NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL\": \"https://discord.com/api/webhooks/...\"\n      }\n    }\n  }\n}\n```\n\n### Available MCP Tools\n\n| Tool | Description | Required Params |\n|------|-------------|-----------------|\n| `send_notification` | Send via any channel (generic) | `channel`, `recipient`, `body` or `template` |\n| `send_email` | Send email | `to`, `body` or `template` |\n| `send_sms` | Send SMS via Twilio | `phone`, `body` or `template` |\n| `send_slack` | Send to Slack channel | `recipient`, `body` or `template` |\n| `send_telegram` | Send via Telegram Bot | `recipient`, `body` or `template` |\n| `send_discord` | Send to Discord channel | `recipient`, `body` or `template` |\n| `send_whatsapp` | Send WhatsApp via Twilio | `phone`, `body` or `template` |\n| `send_teams` | Send to Microsoft Teams | `recipient`, `body` or `template` |\n| `send_google_chat` | Send to Google Chat | `recipient`, `body` or `template` |\n| `send_push` | Send push via Firebase | `push_token`, `body` |\n| `send_twitter` | Post a tweet on Twitter/X | `body` or `template` |\n| `send_linkedin` | Publish a post on LinkedIn | `body` or `template` |\n| `send_notion` | Create a page in Notion | `recipient`, `body` or `template` |\n| `send_twitch` | Send Twitch chat message + polls | `recipient`, `body` or `template` |\n| `send_youtube` | Send YouTube live chat message | `recipient`, `body` or `template` |\n| `send_instagram` | Send Instagram DM or feed post | `recipient`, `body` or `template` |\n| `send_multi_channel` | Send to multiple channels | `channels[]`, `recipient`, `body` or `template` |\n| `send_batch` | Send to multiple recipients at once | `recipients[]`, `channel`, `body` or `template` |\n| `send_to_audience` | Send to a named audience | `audience`, `channel`, `body` or `template` |\n| `list_channels` | List configured channels | _(none)_ |\n| `list_delivery_receipts` | Query delivery history | _(none)_ |\n| `list_dead_letters` | View failed notifications (DLQ) | _(none)_ |\n| `create_contact` | Create a contact with tags | `name` |\n| `list_contacts` | List contacts (filter by tag) | _(none)_ |\n| `create_audience` | Create audience with tag filters | `name`, `tags[]` |\n| `list_audiences` | List audiences with contact counts | _(none)_ |\n| `get_analytics` | Delivery stats by channel/status | _(none)_ |\n| `send_tiktok_shop` | Send TikTok Shop notification | `recipient`, `body` or `template` |\n| `send_facebook` | Send Facebook page post or Messenger DM | `recipient`, `body` or `template` |\n| `check_email_status` | Check SendGrid email delivery status | `message_id` |\n| `schedule_notification` | Schedule a notification for later delivery | `channel`, `recipient`, `body`, `send_at` |\n| `list_scheduled_notifications` | List all scheduled notifications | _(none)_ |\n| `cancel_scheduled_notification` | Cancel a pending scheduled notification | `notification_id` |\n\nAll send tools optionally accept: `subject`, `template`, `params`, `priority`.\n\n### Usage example (from an AI agent)\n\nOnce configured, you can simply ask your AI agent:\n\n> \"Send a Discord message to #alerts saying the deploy is complete\"\n\nThe agent will call the `send_discord` tool with the appropriate parameters.\n\n### Docker\n\nRun the MCP server without Java installed — only Docker required:\n\n```bash\n# Build\ndocker build -t notifyhub-mcp .\n\n# Run with Discord\ndocker run -i --rm \\\n  -e NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL=\"https://discord.com/api/webhooks/...\" \\\n  -e NOTIFY_CHANNELS_DISCORD_USERNAME=\"NotifyHub\" \\\n  gabrielbbal10/notifyhub-mcp\n\n# Run with multiple Discord channels + Email\ndocker run -i --rm \\\n  -e NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL=\"https://discord.com/api/webhooks/...\" \\\n  -e NOTIFY_CHANNELS_DISCORD_USERNAME=\"NotifyHub\" \\\n  -e NOTIFY_CHANNELS_DISCORD_RECIPIENTS_ALERTS=\"https://discord.com/api/webhooks/111/aaa\" \\\n  -e NOTIFY_CHANNELS_DISCORD_RECIPIENTS_DEVOPS=\"https://discord.com/api/webhooks/222/bbb\" \\\n  -e NOTIFY_CHANNELS_EMAIL_HOST=\"smtp.gmail.com\" \\\n  -e NOTIFY_CHANNELS_EMAIL_PORT=\"587\" \\\n  -e NOTIFY_CHANNELS_EMAIL_USERNAME=\"you@gmail.com\" \\\n  -e NOTIFY_CHANNELS_EMAIL_PASSWORD=\"app-password\" \\\n  -e NOTIFY_CHANNELS_EMAIL_FROM=\"you@gmail.com\" \\\n  gabrielbbal10/notifyhub-mcp\n```\n\n**Configure in Claude Code** (`.mcp.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"notify-hub\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\",\n        \"-e\", \"NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...\",\n        \"-e\", \"NOTIFY_CHANNELS_DISCORD_USERNAME=NotifyHub\",\n        \"-e\", \"NOTIFY_CHANNELS_DISCORD_RECIPIENTS_ALERTS=https://discord.com/api/webhooks/111/aaa\",\n        \"gabrielbbal10/notifyhub-mcp\"\n      ]\n    }\n  }\n}\n```\n\n### Docker REST API\n\nRun a full REST API with Swagger UI — no Java required, just Docker:\n\n```bash\ndocker run -d -p 8080:8080 \\\n  -e NOTIFY_CHANNELS_EMAIL_USERNAME=you@gmail.com \\\n  -e NOTIFY_CHANNELS_EMAIL_PASSWORD=your-app-password \\\n  -e NOTIFY_CHANNELS_EMAIL_FROM=you@gmail.com \\\n  gabrielbbal10/notifyhub-api:latest\n```\n\nOpen [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html) for interactive API docs.\n\n**Environment variables — same for both MCP and API images, add only the channels you need:**\n\n| Channel | Variable | Required | Example |\n|---------|----------|----------|---------|\n| **Email** | `NOTIFY_CHANNELS_EMAIL_HOST` | No (default: `smtp.gmail.com`) | `smtp.gmail.com` |\n| | `NOTIFY_CHANNELS_EMAIL_PORT` | No (default: `587`) | `587` |\n| | `NOTIFY_CHANNELS_EMAIL_USERNAME` | Yes (for email) | `you@gmail.com` |\n| | `NOTIFY_CHANNELS_EMAIL_PASSWORD` | Yes (for email) | `abcd efgh ijkl mnop` ([App Password](https://myaccount.google.com/apppasswords)) |\n| | `NOTIFY_CHANNELS_EMAIL_FROM` | Yes (for email) | `you@gmail.com` |\n| **Discord** | `NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL` | Yes (for discord) | `https://discord.com/api/webhooks/...` |\n| | `NOTIFY_CHANNELS_DISCORD_USERNAME` | No | `NotifyHub` |\n| | `NOTIFY_CHANNELS_DISCORD_AVATAR_URL` | No | `https://example.com/avatar.png` |\n| | `NOTIFY_CHANNELS_DISCORD_RECIPIENTS_<NAME>` | No | Named alias webhook URL |\n| **Slack** | `NOTIFY_CHANNELS_SLACK_WEBHOOK_URL` | Yes (for slack) | `https://hooks.slack.com/services/...` |\n| | `NOTIFY_CHANNELS_SLACK_RECIPIENTS_<NAME>` | No | Named alias webhook URL |\n| **Telegram** | `NOTIFY_CHANNELS_TELEGRAM_BOT_TOKEN` | Yes (for telegram) | `123456:ABC-DEF...` |\n| | `NOTIFY_CHANNELS_TELEGRAM_CHAT_ID` | Yes (for telegram) | `123456789` |\n| | `NOTIFY_CHANNELS_TELEGRAM_RECIPIENTS_<NAME>` | No | Named alias chat ID |\n| **Google Chat** | `NOTIFY_CHANNELS_GOOGLE_CHAT_WEBHOOK_URL` | Yes (for gchat) | `https://chat.googleapis.com/v1/spaces/...` |\n| | `NOTIFY_CHANNELS_GOOGLE_CHAT_RECIPIENTS_<NAME>` | No | Named alias webhook URL |\n| **Teams** | `NOTIFY_CHANNELS_TEAMS_WEBHOOK_URL` | Yes (for teams) | `https://outlook.office.com/webhook/...` |\n| | `NOTIFY_CHANNELS_TEAMS_RECIPIENTS_<NAME>` | No | Named alias webhook URL |\n| **SMS** | `NOTIFY_CHANNELS_SMS_ACCOUNT_SID` | Yes (for sms) | Twilio Account SID |\n| | `NOTIFY_CHANNELS_SMS_AUTH_TOKEN` | Yes (for sms) | Twilio Auth Token |\n| | `NOTIFY_CHANNELS_SMS_FROM_NUMBER` | Yes (for sms) | `+12025551234` |\n| **WhatsApp** | `NOTIFY_CHANNELS_WHATSAPP_ACCOUNT_SID` | Yes (for whatsapp) | Twilio Account SID |\n| | `NOTIFY_CHANNELS_WHATSAPP_AUTH_TOKEN` | Yes (for whatsapp) | Twilio Auth Token |\n| | `NOTIFY_CHANNELS_WHATSAPP_FROM_NUMBER` | Yes (for whatsapp) | `+14155238886` |\n\n**Example with multiple channels:**\n\n```bash\ndocker run -d -p 8080:8080 \\\n  -e NOTIFY_CHANNELS_EMAIL_USERNAME=you@gmail.com \\\n  -e NOTIFY_CHANNELS_EMAIL_PASSWORD=your-app-password \\\n  -e NOTIFY_CHANNELS_EMAIL_FROM=you@gmail.com \\\n  -e NOTIFY_CHANNELS_DISCORD_WEBHOOK_URL=\"https://discord.com/api/webhooks/...\" \\\n  -e NOTIFY_CHANNELS_DISCORD_USERNAME=\"NotifyHub\" \\\n  -e NOTIFY_CHANNELS_TELEGRAM_BOT_TOKEN=\"123456:ABC-DEF...\" \\\n  -e NOTIFY_CHANNELS_TELEGRAM_CHAT_ID=\"123456789\" \\\n  -e NOTIFY_CHANNELS_GOOGLE_CHAT_WEBHOOK_URL=\"https://chat.googleapis.com/v1/spaces/...\" \\\n  gabrielbbal10/notifyhub-api:latest\n```\n\n**Usage from any language:**\n\n```bash\n# Send email\ncurl -X POST \"http://localhost:8080/send/email?to=user@example.com&subject=Hello&body=Hi!\"\n\n# Send Discord\ncurl -X POST \"http://localhost:8080/send/discord?message=Deploy done!\"\n\n# Send Telegram\ncurl -X POST \"http://localhost:8080/send/telegram?chatId=123456789&message=Alert!\"\n\n# Send Google Chat\ncurl -X POST \"http://localhost:8080/send/google-chat?message=Build passed!\"\n```\n\n**Docker Hub:** [gabrielbbal10/notifyhub-api](https://hub.docker.com/r/gabrielbbal10/notifyhub-api)\n\n---\n\n## Running the Demo\n\nThe demo app showcases every feature with a built-in SMTP server — **zero external config needed**.\n\n```bash\ngit clone https://github.com/GabrielBBaldez/notify-hub.git\ncd notify-hub\nmvn clean install -DskipTests\n\n# Run the demo\nmvn -pl notify-demo spring-boot:run\n```\n\nThen open:\n- [http://localhost:8080](http://localhost:8080) — API endpoints\n- [http://localhost:8080/notify-admin](http://localhost:8080/notify-admin) — Admin dashboard\n\n### Demo Endpoints\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/` | Home — lists all endpoints |\n| `POST` | `/send/email` | Send a simple email |\n| `POST` | `/send/template` | Send email with Mustache template |\n| `POST` | `/send/notifiable` | Send to a Notifiable entity |\n| `POST` | `/send/sms` | Send SMS (requires Twilio) |\n| `POST` | `/send/whatsapp` | Send WhatsApp (requires Twilio) |\n| `POST` | `/send/telegram` | Send to Telegram via Bot |\n| `POST` | `/send/discord` | Send to Discord via Webhook |\n| `POST` | `/send/slack` | Send to Slack channel |\n| `POST` | `/send/teams` | Send to Microsoft Teams via Webhook |\n| `POST` | `/send/google-chat` | Send to Google Chat via Webhook |\n| `POST` | `/send/push` | Send push notification via Firebase |\n| `POST` | `/send/websocket` | Send message via WebSocket |\n| `POST` | `/send/multi` | Send to email + Slack simultaneously |\n| `POST` | `/send/fallback` | Test fallback (email fails -> Slack) |\n| `POST` | `/send/tracked` | Send with delivery tracking |\n| `POST` | `/send/scheduled` | Schedule notification for future |\n| `GET` | `/tracking` | Delivery tracking history |\n| `GET` | `/notify-admin` | Admin dashboard |\n| `GET` | `/inbox` | View captured emails |\n| `DELETE` | `/inbox` | Clear all inboxes |\n\n---\n\n## Architecture\n\n```\nnotify-hub/\n├── notify-core/                          # Zero Spring dependency\n│   ├── NotifyHub                         # Thin facade — delegates to executor/scheduler/eventbus\n│   ├── NotificationExecutor              # Channel resolution, send logic, fallback chains\n│   ├── NotificationScheduler             # Scheduling with delay/cancel/list\n│   ├── NotificationBuilder               # Fluent builder (send/async/tracked/scheduled)\n│   ├── BatchNotificationBuilder          # Batch send to multiple recipients\n│   ├── Notification                      # Immutable notification object\n│   ├── Channel / ChannelRef              # Built-in + custom channel refs\n│   ├── Priority                          # URGENT, HIGH, NORMAL, LOW\n│   ├── Notifiable                        # Recipient interface (i18n + routing + quiet hours)\n│   ├── NotificationChannel               # Channel SPI (implement this!)\n│   ├── QuietHours                        # Per-recipient quiet time windows\n│   ├── pipeline/                         # Resilience handler chain\n│   │   ├── SendPipeline                  # Assembles: Dedup → RateLimit → CircuitBreaker → Template → Retry\n│   │   ├── SendHandler                   # Handler chain interface\n│   │   ├── SendContext                   # Per-send state (channel, builder, notification)\n│   │   ├── DeduplicationHandler          # Skip duplicates\n│   │   ├── RateLimitHandler              # Enforce per-channel limits\n│   │   ├── CircuitBreakerHandler         # Short-circuit failing channels\n│   │   ├── TemplateHandler               # Build + render notification\n│   │   └── RetrySendHandler              # Terminal: retry with backoff → DLQ\n│   ├── resilience/                       # Resilience primitives\n│   │   ├── ChannelCircuitBreaker         # Per-channel circuit breaker (sliding window)\n│   │   ├── CircuitBreakerConfig          # Thresholds + durations\n│   │   ├── CircuitState                  # CLOSED, OPEN, HALF_OPEN\n│   │   └── BulkheadConfig               # Per-channel concurrency limits\n│   ├── event/                            # Unified event system\n│   │   ├── NotificationEventBus          # Publish events to listeners\n│   │   ├── NotificationEvent             # Immutable event record\n│   │   ├── EventType                     # SENT, FAILED, RETRIED, RATE_LIMITED, DEDUPED, CIRCUIT_*\n│   │   ├── NotificationEventListener     # Listener interface\n│   │   └── LegacyListenerAdapter         # Bridge: old NotificationListener → EventBus\n│   ├── orchestration/                    # Multi-step notification workflows\n│   │   ├── OrchestrationBuilder          # first(EMAIL).ifNoOpen(24h).then(PUSH)\n│   │   └── OrchestrationStep            # Individual step record\n│   ├── abtest/                           # A/B testing\n│   │   └── AbTestBuilder                # Deterministic SHA-256 variant selection\n│   ├── schedule/                         # Cron support\n│   │   └── CronExpression               # Lightweight 5-field cron parser\n│   ├── attachment/                       # File attachments\n│   │   └── Attachment                    # Email file attachments\n│   ├── testing/                          # Test utilities\n│   │   ├── TestNotifyHub                 # Test wrapper with capturing channels\n│   │   └── SentNotification             # Captured notification record\n│   ├── RateLimiter / TokenBucket         # Rate limiting\n│   ├── NotificationRouter / RoutingRule  # Conditional routing\n│   ├── MustacheTemplateEngine            # Template engine (i18n-aware + versioning)\n│   ├── DeduplicationStore                # Dedup interface (in-memory impl)\n│   └── RetryPolicy                       # Retry + backoff strategies\n│\n├── notify-channels/\n│   ├── notify-email/                     # SMTP email (Jakarta Mail + attachments)\n│   ├── notify-sms/                       # Twilio SMS + WhatsApp\n│   ├── notify-slack/                     # Slack webhooks (JDK HttpClient)\n│   ├── notify-telegram/                  # Telegram Bot API (JDK HttpClient)\n│   ├── notify-discord/                   # Discord webhooks (JDK HttpClient)\n│   ├── notify-teams/                     # Microsoft Teams webhooks (JDK HttpClient)\n│   ├── notify-push-firebase/             # Firebase Cloud Messaging (FCM)\n│   ├── notify-webhook/                   # Generic webhook (configurable)\n│   ├── notify-websocket/                 # WebSocket (JDK java.net.http)\n│   ├── notify-google-chat/              # Google Chat webhooks (JDK HttpClient)\n│   ├── notify-twitch/                   # Twitch chat + polls via Helix API (JDK HttpClient)\n│   ├── notify-youtube/                  # YouTube live chat via Data API v3 (JDK HttpClient)\n│   ├── notify-instagram/               # Instagram DM + feed via Meta Graph API (JDK HttpClient)\n│   ├── notify-sendgrid/                # SendGrid email with delivery tracking (JDK HttpClient)\n│   ├── notify-tiktok-shop/             # TikTok Shop API (HMAC-SHA256, JDK HttpClient)\n│   ├── notify-facebook/                # Facebook Graph API (Page + Messenger)\n│   ├── notify-whatsapp/                # WhatsApp Cloud API (Meta Graph API, no Twilio)\n│   ├── notify-aws-sns/                 # AWS SNS (AWS SDK v2)\n│   ├── notify-mailgun/                 # Mailgun transactional email (JDK HttpClient)\n│   ├── notify-pagerduty/              # PagerDuty Events API v2 (JDK HttpClient)\n│   └── notify-kick/                  # Kick Public API (JDK HttpClient)\n│   └── notify-channel-template/       # Template/archetype for creating new channels\n│\n├── notify-tracker-jpa/                   # JPA-backed delivery tracker\n├── notify-audit-jpa/                     # JPA-backed",
  "bytes": 60000,
  "sha": "4c74fcf5a9ed800e656999a8d6e9ffc2899e5fed4427de025568f99ae0ff6c71",
  "repo_slug": "gabrielbbaldez/notify-hub",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_gabrielbbaldez_notify_hub_c3d14f56/readme"
}