One command. Every notification channel.
Project description
notifykit
One command. Every notification channel.
Send notifications to Slack, Discord, Email, Telegram, and Webhooks from a single YAML config.
Why This Exists
Every project eventually needs notifications. Deploy alerts go to Slack. Error pages ping the on-call Telegram group. Weekly reports land in email. Monitoring webhooks fire to custom dashboards.
The problem: each channel has its own SDK, authentication flow, payload format, and retry logic. You end up writing the same boilerplate in every project — a Slack webhook here, a SendGrid integration there, a Telegram bot scattered somewhere else.
notifykit solves this by providing a single unified interface. One YAML config file declares all your channels. One CLI command — or one Python function — routes messages to the right places based on severity level and tags. Rate limiting, retries, formatting, and audit logging come built-in.
No more copy-pasting webhook code. No more forgetting to add retries. No more wondering which notification actually got delivered.
Table of Contents
- Features
- Installation
- Quick Start
- Configuration
- CLI Reference
- Python API
- Channel Details
- Custom Channels
- Architecture
- When to Use This
- Example Output
- Troubleshooting
- FAQ
- Testing
- Contributing
- License
Features
- 6 built-in channels — Slack, Discord, Email, Telegram, Webhook, Console
- YAML configuration — One file declares all channels with
${ENV_VAR}expansion - Level-based routing — Send debug to console, errors to Slack, critical to PagerDuty webhook
- Tag-based routing — Route
deploytags to one channel,alertsto another - Template engine —
{{variable}}substitution with dotted access ({{deploy.env.name}}) - Multi-format output — Plain text, Markdown, HTML — each channel gets the right format
- Rate limiting — Per-channel token bucket prevents flooding
- Retry with backoff — Exponential backoff with configurable max delay
- Audit log — Every send attempt recorded with timestamp, result, and timing
- Dry-run mode — See which channels would receive a message without sending
- Extensible — Register custom channels with a simple ABC
- CLI-first — Every feature accessible from the command line
Installation
pip install notifykit
Or install from source:
git clone https://github.com/JSLEEKR/notifykit.git
cd notifykit
pip install -e ".[dev]"
Requirements
- Python 3.10+
click(CLI framework)pyyaml(configuration)requests(HTTP channels)
Quick Start
1. Generate a config file
notifykit init
This creates notifykit.yaml with a console channel enabled and all other channels commented out as examples.
2. Send a notification
notifykit send "Hello from notifykit!" --level info
3. Send with title and tags
notifykit send "Deployment complete" --title "Deploy v1.2.3" --tag deploy --tag prod --level info
4. Test all channels
notifykit test
Configuration
notifykit uses a YAML file (notifykit.yaml) to declare channels and their settings.
Minimal config
channels:
console:
type: console
enabled: true
levels: [debug, info, warning, error, critical]
Full config example
channels:
console:
type: console
enabled: true
levels: [debug, info, warning, error, critical]
slack-alerts:
type: slack
enabled: true
levels: [warning, error, critical]
tags: [alerts]
settings:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: "#alerts"
username: notifykit
discord-dev:
type: discord
enabled: true
levels: [info, warning, error, critical]
settings:
webhook_url: ${DISCORD_WEBHOOK_URL}
email-ops:
type: email
enabled: true
levels: [error, critical]
settings:
smtp_host: smtp.gmail.com
smtp_port: 587
use_tls: true
username: ${EMAIL_USER}
password: ${EMAIL_PASS}
from_addr: alerts@example.com
to_addrs:
- ops@example.com
telegram-oncall:
type: telegram
enabled: true
levels: [critical]
tags: [oncall]
settings:
bot_token: ${TELEGRAM_BOT_TOKEN}
chat_id: ${TELEGRAM_CHAT_ID}
webhook-monitor:
type: webhook
enabled: true
settings:
url: https://monitor.example.com/api/events
method: POST
headers:
Authorization: "Bearer ${MONITOR_TOKEN}"
defaults:
format: plain
retry_count: 2
retry_delay: 1.0
templates:
deploy: "Deployment {{status}}: {{service}} ({{version}}) at {{timestamp}}"
alert: "[{{level}}] {{title}}: {{body}}"
Channel Types
| Type | Description | Required Settings |
|---|---|---|
console |
Print to stdout/stderr | None |
slack |
Slack incoming webhook | webhook_url |
discord |
Discord webhook | webhook_url |
email |
SMTP email | smtp_host, from_addr, to_addrs |
telegram |
Telegram Bot API | bot_token, chat_id |
webhook |
Generic HTTP webhook | url |
Environment Variables
Use ${VAR_NAME} syntax in your config to reference environment variables:
settings:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: ${SLACK_CHANNEL:-#general} # with default value
The ${VAR:-default} syntax provides a fallback when the variable is not set.
Level-Based Routing
Each channel declares which severity levels it accepts:
channels:
console:
levels: [debug, info, warning, error, critical] # everything
slack:
levels: [warning, error, critical] # warnings and above
pagerduty:
levels: [critical] # critical only
Available levels: debug, info, warning, error, critical
Tag-Based Routing
Channels can filter by message tags. If a channel has tags configured, only messages with at least one matching tag are routed to it:
channels:
slack-deploy:
tags: [deploy, release] # only deploy/release messages
slack-alerts:
tags: [alerts, monitoring] # only alert messages
console:
# no tags = receives everything that matches level filter
CLI Reference
send
Send a notification message to all matching channels.
notifykit send "Message body" [OPTIONS]
| Option | Description |
|---|---|
-l, --level |
Severity level: debug/info/warning/error/critical (default: info) |
-t, --title |
Message title |
--tag |
Add a tag (repeatable) |
-f, --format |
Output format: plain, markdown, html |
-c, --config |
Path to config file |
--channel |
Send to specific channel(s) only |
--template |
Use a named template from config |
--var |
Template variable as key=value (repeatable) |
--dry-run |
Show routing without actually sending |
--json |
Output results as JSON (for CI pipelines) |
-q, --quiet |
Suppress non-error output |
Examples:
# Basic send
notifykit send "Server is healthy" -l info
# With title and tags
notifykit send "CPU at 95%" -t "High CPU Alert" --tag alerts -l warning
# Using a template
notifykit send "" --template deploy --var status=success --var service=api --var version=1.2.3
# Dry run
notifykit send "Test message" --dry-run
# Send to specific channel only
notifykit send "Debug info" --channel console
test
Send a test notification to all enabled channels to verify configuration.
notifykit test [-c CONFIG]
channels
List all configured channels with their type, status, levels, and tags.
notifykit channels [-c CONFIG]
validate
Validate the configuration file for errors.
notifykit validate [-c CONFIG]
Returns exit code 0 if valid, 1 if issues are found.
history
Show notification history from the audit log.
notifykit history [OPTIONS]
| Option | Description |
|---|---|
-n, --limit |
Number of entries (default: 20) |
--channel |
Filter by channel name |
--failures |
Show only failed sends |
--json |
Output as JSON |
init
Generate a default notifykit.yaml configuration file.
notifykit init [-o FILENAME] [--force]
Python API
Sending Messages
from notifykit.models import Message, Level
from notifykit.config import load_config, parse_channels
from notifykit.exceptions import ConfigNotFoundError
from notifykit.router import Router
# Load config and create router
try:
config = load_config("notifykit.yaml")
except ConfigNotFoundError:
print("Run 'notifykit init' first")
raise
channels = parse_channels(config)
router = Router(channels)
# Create and send a message
msg = Message(
body="Deployment complete",
level=Level.INFO,
title="Deploy v1.2.3",
tags=["deploy", "prod"],
)
results = router.send(msg)
for r in results:
print(r.summary) # "[OK] slack (120ms)" or "[FAIL] email - SMTP error"
Templates
from notifykit.templates import render, list_variables, validate_template
template = "Deployment {{status}}: {{service}} ({{version}})"
variables = {"status": "success", "service": "api", "version": "1.2.3"}
# Render
output = render(template, variables)
# "Deployment success: api (1.2.3)"
# List variables in template
vars = list_variables(template)
# ["status", "service", "version"]
# Validate all variables are available
missing = validate_template(template, variables)
# [] (empty = all resolved)
Dotted access is supported:
render("{{user.name}}", {"user": {"name": "Alice"}})
# "Alice"
Formatting
from notifykit.formatter import format_message, convert_format
from notifykit.models import Format, Message
msg = Message(body="Hello", title="Test", level="error", tags=["prod"])
plain = format_message(msg, Format.PLAIN)
# "[ERROR] Test Hello (tags: prod)"
md = format_message(msg, Format.MARKDOWN)
# "## Test\n\n**Level:** ERROR\n\nHello\n\n**Tags:** `prod`"
html = format_message(msg, Format.HTML)
# "<div>...<h2>Test</h2>...[ERROR]...<p>Hello</p>...</div>"
# Convert between formats
convert_format("**bold**", Format.MARKDOWN, Format.PLAIN)
# "bold"
Rate Limiting
from notifykit.rate_limiter import RateLimiter
rl = RateLimiter()
rl.configure("slack", max_per_minute=10)
if rl.allow("slack"):
# send message
pass
else:
wait = rl.wait_time("slack")
print(f"Rate limited. Wait {wait:.1f}s")
Retry Logic
from notifykit.retry import retry_send
result = retry_send(
channel.send,
message,
max_retries=3,
base_delay=1.0,
backoff_factor=2.0,
)
Delay schedule: 1s, 2s, 4s, 8s, ... capped at max_delay.
Audit Log
from notifykit.audit import AuditLog
audit = AuditLog("notifications.jsonl")
# Record send results
audit.record(result)
audit.record_many(results)
# Query history
recent = audit.query(limit=10)
failures = audit.query(success=False)
slack_history = audit.query(channel="slack")
# Statistics
stats = audit.stats()
print(f"Success rate: {stats['success_rate']:.0%}")
print(f"Total: {stats['total']}, Failed: {stats['failed']}")
Channel Details
Slack
Sends via Incoming Webhooks with Block Kit formatting.
slack-alerts:
type: slack
levels: [warning, error, critical]
settings:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: "#alerts" # optional override
username: notifykit # optional bot name
Features: header blocks, section blocks, context blocks with tags, level-specific emoji.
Discord
Sends via Discord Webhooks with rich embeds.
discord-dev:
type: discord
settings:
webhook_url: ${DISCORD_WEBHOOK_URL}
username: NotifyBot # optional
Features: colored embeds by severity, title/description, footer with tags.
Email (SMTP)
Sends via any SMTP server with TLS support.
email-ops:
type: email
levels: [error, critical]
settings:
smtp_host: smtp.gmail.com
smtp_port: 587
use_tls: true
username: ${EMAIL_USER}
password: ${EMAIL_PASS}
from_addr: alerts@example.com
to_addrs:
- ops@example.com
- oncall@example.com
Features: HTML and plain text MIME, TLS, authentication optional.
Telegram
Sends via Telegram Bot API.
telegram-oncall:
type: telegram
levels: [critical]
settings:
bot_token: ${TELEGRAM_BOT_TOKEN}
chat_id: ${TELEGRAM_CHAT_ID}
disable_preview: true # optional
Features: HTML and MarkdownV2 parse modes, hashtag-style tags, web preview control.
Webhook
Generic HTTP webhook for any endpoint.
webhook-monitor:
type: webhook
settings:
url: https://monitor.example.com/api/events
method: POST # default
timeout: 10 # seconds
headers:
Authorization: "Bearer ${TOKEN}"
success_codes: [200, 201, 202, 204] # default
The payload includes: id, level, title, body, tags, timestamp, metadata.
Console
Prints to stdout (info/debug/warning) or stderr (error/critical) with ANSI colors.
console:
type: console
settings:
color: true # default
Custom Channels
Register your own channel by extending BaseChannel:
from notifykit.channels.base import BaseChannel
from notifykit.channels.registry import register_channel
from notifykit.models import Message, SendResult
class PagerDutyChannel(BaseChannel):
def send(self, message: Message) -> SendResult:
# Your implementation here
api_key = self.config.settings.get("api_key")
# ... send to PagerDuty ...
return self._make_result(message, success=True)
register_channel("pagerduty", PagerDutyChannel)
Then use it in your config:
channels:
pagerduty:
type: pagerduty
levels: [critical]
settings:
api_key: ${PAGERDUTY_KEY}
Architecture
src/notifykit/
__init__.py # Package version
models.py # Message, SendResult, Level, ChannelConfig
config.py # YAML loader, env expansion, validation
exceptions.py # Typed exception hierarchy
router.py # Level/tag routing engine
formatter.py # Plain/Markdown/HTML formatting
templates.py # {{variable}} template engine
rate_limiter.py # Token bucket rate limiting
retry.py # Exponential backoff retry
audit.py # Notification audit log
cli.py # Click CLI commands
channels/
__init__.py
base.py # BaseChannel ABC + error sanitization
registry.py # Channel type registry
console.py # Console (stdout/stderr)
slack.py # Slack incoming webhook
discord.py # Discord webhook
email_channel.py # SMTP email
telegram.py # Telegram Bot API
webhook.py # Generic HTTP webhook
Message Flow
CLI/API --> Router --> Channel --> Destination
| | |
| Level/Tag Rate Limit
| Matching + Retry
| | |
+-- Template | Audit Log
Rendering |
|
Formatter
(Plain/MD/HTML)
When to Use This
| Scenario | What to configure | Command |
|---|---|---|
| Deploy pipeline alerts | Slack channel for deploy tag, warn level+ |
notifykit send "Deploy done" --tag deploy --level info |
| On-call paging | Telegram with oncall tag, critical only |
notifykit send "DB down" --tag oncall --level critical |
| Daily reports | Email on a schedule (cron + notifykit) | notifykit send "Daily report ready" --tag report |
| Error monitoring | Multiple channels, error+ level | notifykit send "500 spike" --level error |
| Local dev debugging | Console channel only | notifykit send "Checkpoint hit" --level debug |
| Webhook integrations | Custom webhook channel with Bearer auth | notifykit send "Event fired" --channel webhook-monitor |
| CI/CD build status | Discord embed + Slack, with templates | notifykit send "" --template deploy --var status=passed |
| Security alerts | Slack + Telegram + Email, critical only | notifykit send "Auth bypass detected" --level critical |
notifykit shines when you need to send notifications to multiple channels from scripts, CI pipelines, or background workers — without installing five different SDKs.
Cross-Tool Integration
CI/CD Pipelines
GitHub Actions
- name: Notify on deploy
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
notifykit send "Deploy ${{ github.sha }}" \
--title "Deploy to production" \
--level info --tag deploy --json
Use --json for machine-readable output. Exit code 1 on any channel failure.
See examples/github-actions.yml for a complete workflow.
GitLab CI
notify:
stage: notify
script:
- pip install notifykit
- notifykit send "Pipeline $CI_PIPELINE_ID finished" --level info --tag deploy --json
when: always
Jenkins
post {
success {
sh 'notifykit send "Build #${BUILD_NUMBER} passed" --level info --tag ci --json'
}
failure {
sh 'notifykit send "Build #${BUILD_NUMBER} failed" --level error --tag ci --tag alerts --json'
}
}
Cron Jobs
Run notifykit from cron for scheduled notifications:
# Daily health check report at 9am
0 9 * * * cd /app && notifykit send "Daily health check passed" --level info --tag report
# Alert if disk usage exceeds 90%
*/5 * * * * df -h / | awk 'NR==2 && $5+0 > 90 {print "Disk usage: "$5}' | xargs -I{} notifykit send "{}" --level warning --tag alerts
Monitoring Tools
Prometheus Alertmanager
Configure Alertmanager to call notifykit via a webhook receiver:
# alertmanager.yml
receivers:
- name: notifykit
webhook_configs:
- url: 'http://localhost:8080/alert' # your bridge script
Bridge script:
#!/bin/bash
# alertmanager-bridge.sh — receives Alertmanager webhook, forwards via notifykit
BODY=$(cat)
ALERT_NAME=$(echo "$BODY" | jq -r '.alerts[0].labels.alertname')
STATUS=$(echo "$BODY" | jq -r '.alerts[0].status')
notifykit send "$ALERT_NAME: $STATUS" --level error --tag alerts --json
Systemd Service Watchdog
# /etc/systemd/system/notifykit-watchdog.service
[Unit]
Description=Notify on service failure
[Service]
ExecStart=/usr/local/bin/notifykit send "Service %i failed" --level critical --tag oncall
Type=oneshot
[Install]
WantedBy=multi-user.target
# Trigger on another service failure
systemctl enable notifykit-watchdog@myapp.service
Docker Health Checks
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || \
(notifykit send "Container unhealthy" --level error --tag alerts && exit 1)
Python Integration
# Use as a library in your application
from notifykit.config import load_config, parse_channels
from notifykit.router import Router
from notifykit.models import Message, Level
router = Router(parse_channels(load_config()))
# In your error handler
def on_error(error: Exception):
router.send(Message(
body=str(error),
level=Level.ERROR,
title="Application Error",
tags=["alerts"],
))
Example Output
notifykit init
Created notifykit.yaml
Next steps:
1. Open notifykit.yaml and configure your channels
2. Uncomment and fill in the channels you want to use
3. Run 'notifykit send "Hello!"' to send your first notification
4. Run 'notifykit channels' to see all configured channels
5. Run 'notifykit validate' to check your config for errors
The console channel is already enabled - try sending a message now!
notifykit send "Deploy complete" --title "v1.2.3" --level info
[INFO] v1.2.3 Deploy complete
[OK] console (0ms)
[OK] slack-alerts (120ms)
notifykit send "Test" --dry-run
Would send to 2 channel(s):
- console (console)
- slack-alerts (slack)
notifykit channels
Channels (3):
console
Type: console
Status: enabled
Levels: debug, info, warning, error, critical
slack-alerts
Type: slack
Status: enabled
Levels: warning, error, critical
Tags: alerts
telegram-oncall
Type: telegram
Status: enabled
Levels: critical
Tags: oncall
notifykit validate
Configuration is valid.
Or on failure:
Found 2 issue(s):
- Channel 'slack-alerts': Slack requires 'settings.webhook_url'
- Channel 'email-ops': Email requires 'settings.smtp_host'
notifykit history
[2026-03-28 07:35:23] OK console msg=50c70d432e9e (0ms)
[2026-03-28 07:35:20] OK slack-alerts msg=a3b4f6e0ef23 (120ms)
[2026-03-28 07:34:01] FAIL telegram-oncall msg=f1b2c3d4e5f6 (5001ms)
Error: Connection timeout
notifykit test
[INFO] Test Notification This is a test notification from notifykit.
[OK] console
[OK] slack-alerts
[FAIL] email-ops
Error: SMTP authentication failed
Troubleshooting
No config file found
Error: No notifykit config file found. Create notifykit.yaml or run 'notifykit init'.
Fix: Run notifykit init in your project root to create a default config.
Channel not receiving messages
Symptoms: send exits 0 but the channel is silent.
Check list:
- Run
notifykit channels— is the channelenabled? - Does the message level match the channel's
levelslist? (warningwon't route to a channel that only acceptserror, critical) - Does the message have a tag that matches the channel's
tagsfilter? (Channel withtags: [deploy]only receives messages with--tag deploy) - Run
notifykit send "Test" --dry-runto see which channels would receive it.
Slack / Discord webhook fails
[FAIL] slack-alerts
Error: 400 Bad Request
Fixes:
- Verify the webhook URL is correct:
notifykit validate - Check that
${SLACK_WEBHOOK_URL}is exported in your environment - Test the webhook directly:
curl -X POST -H 'Content-type: application/json' --data '{"text":"test"}' "$SLACK_WEBHOOK_URL"
Email SMTP authentication error
[FAIL] email-ops
Error: SMTP authentication failed
Fixes:
- For Gmail: use an App Password, not your regular password
- Verify
smtp_host,smtp_port, anduse_tlsmatch your provider's settings - Check environment variables are set:
echo $EMAIL_USER
Telegram: chat not found
[FAIL] telegram-oncall
Error: 400 Bad Request: chat not found
Fixes:
- The bot must be added to the chat/group before it can send messages
chat_idfor a group is negative (e.g.,-1001234567890) — use@userinfobotto find it- Send a message to the bot first to activate it for private chats
Environment variable not expanded
If you see ${SLACK_WEBHOOK_URL} literally in error messages, the variable is not exported.
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
notifykit validate
Use ${VAR:-fallback} syntax to provide a default value for optional variables.
notifykit history shows nothing
The audit log is written to .notifykit/audit.jsonl in the current working directory where you ran notifykit send. Run history from the same directory.
Rate limit exceeded
No channels matched for this message.
If you configured a rate_limit on a channel and are sending too fast, the router will skip that channel. The rate limit resets after one minute.
FAQ
Q: Does notifykit make real HTTP requests during tests?
No. All channel tests use mocked HTTP via the responses library. No API calls are made during pytest.
Q: Can I use notifykit without a config file?
No. notifykit requires a notifykit.yaml config file. Run notifykit init to create one with a working console channel — no credentials needed.
Q: Can I send to multiple specific channels at once?
Yes. Use --channel multiple times:
notifykit send "Alert!" --channel slack-alerts --channel telegram-oncall
Q: How do I send the same message to all channels regardless of level/tag filters?
Use a channel with no levels or tags restrictions, or use --channel to target channels directly.
Q: Where is the audit log stored?
At .notifykit/audit.jsonl relative to the working directory. Each line is a JSON record of one send attempt.
Q: How do I rotate or clear the audit log?
Delete or truncate .notifykit/audit.jsonl manually. notifykit will create a new one on the next send.
Q: Does notifykit support async sending?
Not natively — sends are synchronous. For high-volume use, call notifykit from a task queue worker.
Q: Can I add my own channel type?
Yes. See Custom Channels for a step-by-step example.
Q: What happens if a channel fails? Does it block other channels?
No. Each channel is sent independently. A failure in one channel does not block others. All results are collected and reported at the end. The exit code is non-zero only if at least one channel failed.
Migration Guide
From email-only notifications
If you currently send notifications through email (e.g., Python smtplib, SendGrid API):
- Run
notifykit initto create a config - Configure the email channel with your SMTP settings
- Add additional channels (Slack, Discord) for instant alerts
- Replace your email-sending code with
router.send(message)
Before:
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("Server is down!")
msg["Subject"] = "CRITICAL ALERT"
msg["From"] = "alerts@example.com"
msg["To"] = "ops@example.com"
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(user, password)
server.sendmail("alerts@example.com", ["ops@example.com"], msg.as_string())
server.quit()
After:
from notifykit.config import load_config, parse_channels
from notifykit.router import Router
from notifykit.models import Message, Level
router = Router(parse_channels(load_config()))
router.send(Message(
body="Server is down!",
level=Level.CRITICAL,
title="CRITICAL ALERT",
tags=["alerts"],
))
# Now also goes to Slack + Telegram based on your config
From Slack webhook scripts
If you currently call Slack webhooks directly:
- Move your webhook URL to an environment variable
- Configure a Slack channel in
notifykit.yaml - Replace
requests.post(webhook_url, json=...)withnotifykit send
Before:
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Deploy complete"}' \
"$SLACK_WEBHOOK_URL"
After:
notifykit send "Deploy complete" --level info --tag deploy
From multiple notification libraries
If your project uses different libraries for each channel (slack-sdk, python-telegram-bot, etc.):
- Remove individual channel dependencies
- Add
notifykitas a single dependency - Configure all channels in one YAML file
- Replace all send calls with
router.send()
Benefits: unified retry logic, rate limiting, audit logging, and a single config file.
Advanced Usage
Custom Payload for Webhooks
Override the default JSON payload for webhook channels:
channels:
pagerduty:
type: webhook
levels: [critical]
settings:
url: https://events.pagerduty.com/v2/enqueue
headers:
Content-Type: application/json
payload:
routing_key: ${PAGERDUTY_KEY}
event_action: trigger
payload:
summary: "{{body}}"
severity: critical
source: notifykit
Rate Limiting Configuration
Prevent flooding channels with per-channel rate limits:
channels:
slack-alerts:
type: slack
rate_limit: 10 # max 10 messages per minute
settings:
webhook_url: ${SLACK_WEBHOOK_URL}
Template Variables from Environment
Combine templates with environment variables for dynamic notifications:
export DEPLOY_ENV=production
export DEPLOY_VERSION=2.1.0
notifykit send "" \
--template deploy \
--var "env=$DEPLOY_ENV" \
--var "version=$DEPLOY_VERSION" \
--var "status=success" \
--tag deploy
Quiet Mode for Scripts
Use --quiet to suppress output in scripts that only need the exit code:
notifykit send "Health check passed" --level info --quiet
if [ $? -eq 0 ]; then
echo "Notifications sent successfully"
fi
JSON Output for CI Parsing
Parse send results in CI pipelines:
RESULT=$(notifykit send "Build passed" --level info --json)
SUCCESS=$(echo "$RESULT" | jq -r '.success')
FAILED=$(echo "$RESULT" | jq -r '.failed')
if [ "$SUCCESS" = "false" ]; then
echo "Warning: $FAILED channel(s) failed"
fi
See examples/real-world.py for Python integration patterns including FastAPI error handlers, batch notifications, and escalation configurations.
Testing
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run specific test module
pytest tests/test_channels.py
# Run specific test class
pytest tests/test_channels.py::TestSlackChannel
All channel tests use mock HTTP (responses library) — no real API calls are made during testing.
Test Coverage
| Module | Tests | Coverage |
|---|---|---|
| models | 16 | Level, Format, Message, SendResult (summary), ChannelConfig |
| config | 32 | Env expansion, validation, input sanitization, typed exceptions |
| channels | 61 | All 6 channels + registry + base + edge cases + security sanitization |
| router | 11 | Level routing, tag routing, direct send |
| formatter | 15 | Plain, Markdown, HTML, format conversion |
| templates | 16 | Render, dotted access, variables, validation |
| rate_limiter | 11 | Token bucket, reset, wait time |
| retry | 9 | Success, failure, backoff calculation |
| audit | 19 | Record, query, stats, file I/O, skipped line tracking |
| cli | 21 | All 6 commands + version + JSON output + quiet mode |
| integration | 8 | Full pipeline: config -> router -> channel -> audit |
| performance | 8 | Batch 100, rate limiter under load |
| stress | 8 | 1000 notifications, multi-channel, large payloads |
| Total | 260 |
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing) - Write tests for new functionality
- Ensure all tests pass (
pytest) - Submit a pull request
Development Setup
git clone https://github.com/JSLEEKR/notifykit.git
cd notifykit
pip install -e ".[dev]"
pytest
Round Log
| Round | Perspective | Focus | Tests | Commit |
|---|---|---|---|---|
| 1 | User | Onboarding — init guidance, error messages | 201 | fix: user onboarding |
| 2 | User | Troubleshooting docs + examples | 201 | docs: troubleshooting + examples |
| 3 | Developer | Coverage gaps — edge cases across modules | 212 | test: coverage gaps |
| 4 | Developer | Code quality — dead code, unused imports | 212 | refactor: code quality |
| 5 | Security | Input validation — env var injection, depth limits | 218 | fix: input validation |
| 6 | Security | Error recovery — connection/timeout handling | 222 | fix: error recovery |
| 7 | Ecosystem | CI integration — --json flag, GitHub Actions |
222 | feat: CI integration |
| 8 | Ecosystem | Cross-tool docs — GitLab, Jenkins, cron, systemd | 222 | docs: cross-tool integration |
| 9 | Production | Performance — 100-batch, rate limiter stress | 230 | test: performance |
| 10 | Production | Cycle 1 complete — badge, changelog | 230 | docs: cycle 1 complete |
| 11 | User C2 | UX refinements — --quiet flag, dry-run detail |
230 | fix: UX refinements |
| 12 | User C2 | Advanced docs — migration guide, real-world examples | 230 | docs: advanced guide |
| 13 | Developer C2 | Cycle 1 review — SendResult.summary, audit tracking | 234 | refactor: cycle 1 review |
| 14 | Developer C2 | Test organization — helpers module, shared fixtures | 234 | refactor: test organization |
| 15 | Security C2 | Security hardening — secret redaction in errors | 240 | fix: security hardening |
| 16 | Security C2 | Error audit — typed exception hierarchy | 244 | fix: error audit |
| 17 | Ecosystem C2 | Integration tests — full send pipeline | 252 | test: integration |
| 18 | Ecosystem C2 | Doc-code sync — README matches actual API | 252 | docs: doc-code sync |
| 19 | Production C2 | Stress tests — 1000 notifications, large payloads | 260 | test: stress |
| 20 | Production C2 | Final — badges, ROUND_LOG, CHANGELOG | 260 | docs: final polish |
License
MIT - JSLEEKR 2026
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jsleekr_notifykit-1.2.0.tar.gz.
File metadata
- Download URL: jsleekr_notifykit-1.2.0.tar.gz
- Upload date:
- Size: 70.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
263729e67b396c81b5b6bede6d2ddea176427236b609d1f49e165a8dff842e1f
|
|
| MD5 |
6c81e9648e64e0740dd238f5a7f7e5f5
|
|
| BLAKE2b-256 |
94443edaa140857fc879aab23936a31b998ab1ff8eb5bc41b8a423d92463c412
|
File details
Details for the file jsleekr_notifykit-1.2.0-py3-none-any.whl.
File metadata
- Download URL: jsleekr_notifykit-1.2.0-py3-none-any.whl
- Upload date:
- Size: 37.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
050b284258a4b3662e5e7f053bb8bdc8d20e2e1fc45925038d20adf9af7053e6
|
|
| MD5 |
a62965ecfa2888138c1e515b2a20a770
|
|
| BLAKE2b-256 |
d6dde3fb21954f98f35783a96bf1f85901cf81dc30f60dc9417b2c1a7a944e25
|