Notify: The Missing Link Between Your CLI Tools and Team Notifications
Hook
Every security researcher's workflow has a notification gap: your reconnaissance scripts find 10,000 subdomains at 3 AM, but you only discover them when you check the terminal the next morning. Notify solves this by turning any CLI output into actionable alerts across messaging platforms.
Context
Modern DevOps and security workflows are built on composable CLI tools. You pipe subfinder into httpx into nuclei, or chain together custom scripts that process logs, scan infrastructure, or monitor APIs. These pipelines produce valuable data, but there's always a last-mile problem: getting actionable results in front of humans who need to respond.
Historically, developers solved this with brittle bash scripts that curl webhook URLs, hardcoded Slack tokens in shell scripts, or custom Python wrappers for each notification service. Every team reinvented the same wheel, and every tool required its own notification logic. ProjectDiscovery's Notify emerged from this friction, originally designed to stream output from their reconnaissance tools (subfinder, nuclei, httpx) to bug bounty hunters' Slack channels. It's evolved into a universal notification adapter that sits at the end of any pipeline, accepting stdin or file input and routing it to 9+ messaging platforms with minimal configuration overhead.
Technical Insight
Notify's architecture is deliberately minimal: it's a single Go binary with no dependencies that reads data, applies transformations, and fires HTTP requests or native protocol calls to messaging APIs. The core design pattern is provider abstraction—each messaging service implements a common interface that handles authentication, rate limiting, and message formatting.
The tool shines in its configuration flexibility. A single YAML file defines multiple provider instances with unique IDs, allowing granular routing decisions. Here's a realistic security automation example:
slack:
- id: "recon-channel"
slack_channel: "subdomain-recon"
slack_username: "recon-bot"
slack_format: "{{.}}"
slack_webhook_url: "https://hooks.slack.com/services/T00/B00/XX"
- id: "vulns-critical"
slack_channel: "security-alerts"
slack_username: "nuclei-scanner"
slack_format: "*CRITICAL*: {{.}}"
slack_webhook_url: "https://hooks.slack.com/services/T00/B00/YY"
discord:
- id: "bug-bounty"
discord_channel: "findings"
discord_username: "bounty-bot"
discord_format: "```{{.}}```"
discord_webhook_url: "https://discord.com/api/webhooks/123/token"
With this configuration, you can route different tool outputs to appropriate channels using the -id flag. A reconnaissance pipeline might look like:
# Send new subdomains to recon channel
subfinder -d example.com -silent | notify -id recon-channel
# Scan for vulnerabilities and alert critical findings
cat domains.txt | httpx -silent | nuclei -t cves/ -severity critical \
| notify -id vulns-critical
# Post all bounty findings to Discord
cat findings.txt | notify -id bug-bounty -bulk
The template system leverages Go's text/template package with Sprig functions, enabling sophisticated message formatting. You can parse JSON output, extract fields, and construct custom payloads:
# Nuclei outputs JSON, extract and format specific fields
nuclei -json -u https://example.com | jq -r '.info.severity + ": " + .info.name' \
| notify -id vulns-critical
For webhook integrations beyond the built-in providers, Notify supports custom webhooks with template-based JSON construction:
custom:
- id: "jira-tickets"
custom_webhook_url: "https://company.atlassian.net/rest/api/2/issue"
custom_method: "POST"
custom_headers:
Authorization: "Bearer token"
Content-Type: "application/json"
custom_format: |
{
"fields": {
"project": {"key": "SEC"},
"summary": "{{.}}",
"issuetype": {"name": "Bug"}
}
}
The bulk mode (-bulk) is particularly useful for high-volume scenarios. Instead of sending one notification per line, it aggregates input and sends a single message, respecting character limits (configurable via -char-limit). This prevents notification spam when processing thousands of results.
Rate limiting is built-in with the -rate-limit flag, which controls requests per minute to avoid hitting API limits. Combined with -delay for spacing between messages, Notify handles production-scale notification loads without manual throttling logic:
# Process 10,000 subdomains with rate limiting
cat massive-subdomain-list.txt | notify -id recon-channel \
-rate-limit 20 -delay 100 -bulk
The proxy support (-proxy) enables notifications from restricted networks or through corporate proxies, and the silent mode (-silent) suppresses stdout, making it truly pipe-friendly for complex workflows where you need clean output for downstream processing.
Gotcha
Notify's simplicity comes with tradeoffs that matter in production environments. The most critical limitation is the absence of delivery guarantees. If a webhook fails—whether from network issues, API rate limits, or service outages—the notification is lost forever. There's no queue, no retry mechanism, no dead letter storage. For security alerts or compliance-critical notifications, this is unacceptable. You're trusting that your network and the remote service are both reliable at the exact moment your pipeline runs.
The character limit handling is another sharp edge. By default, Notify truncates messages at 4000 characters. It doesn't intelligently split content across multiple messages or provide warnings about truncation. If your nuclei scan outputs a verbose JSON result or your subdomain list exceeds the limit, you'll silently lose data. You must manually chunk data or use -bulk mode carefully, understanding that bulk aggregation can still hit limits with large datasets. Additionally, the YAML configuration stores sensitive webhook URLs and API tokens in plaintext. There's no native support for environment variable substitution or secret management integration. You're responsible for securing the config file through filesystem permissions or external secret injection, which adds operational complexity in containerized or multi-tenant environments.
Verdict
Use Notify if you're running ad-hoc security reconnaissance, bug bounty workflows, or DevOps automation where notifications are informational rather than mission-critical. It excels when you need quick integration with minimal setup—getting subfinder results into Slack in under five minutes, or routing various tool outputs to different Discord channels based on severity. The template system and multi-provider support make it ideal for teams that want flexible notification logic without writing custom integration code. It's perfect for personal projects, small security teams, or scenarios where losing occasional notifications is acceptable. Skip Notify if you need guaranteed delivery for compliance alerts, audit trails, or production incident management. If you're processing sensitive data that requires encryption at rest, need complex routing logic with conditional delivery, or want message queuing with replay capabilities, invest in a proper message broker like RabbitMQ or a notification service like Apprise with persistent storage. Also skip it if you're building multi-tenant systems where credential management and security isolation are paramount—Notify's plaintext config and lack of secret management won't meet security requirements.