Overview
Haven Notify is an internal Go service that validates notification requests, renders Discord-compatible payloads, and delivers them to a configured Discord webhook.
It runs as a standalone binary or container. Templates are embedded in the binary and validated at startup.
Prerequisites
- Go 1.25.10
- A Docker API compatible with Testcontainers for the end-to-end suite
- A Discord webhook URL for normal service operation
WEBHOOK_URL is required and must be an absolute HTTP or HTTPS URL. The process exits before becoming ready when the configuration or embedded templates are invalid.
API
All notification endpoints require POST and Content-Type: application/json. Request bodies are limited to 64 KiB, documented fields are required, and unknown fields are accepted for forward compatibility.
Send Notification
- Endpoint:
/notify - Request Body:
{
"title": "Notification Title",
"message": "Notification Message"
}
Send Backup Notification
- Endpoint:
/template/notify/backup - Request Body:
{
"title": "Nightly backup",
"asset": "Photos",
"backupSizeInMB": 1536,
"extra": [
{
"name": "Host",
"value": "nebula"
}
]
}
Send Update Notification
- Endpoint:
/template/notify/update - Request Body:
{
"host": "nexus",
"asset": "k3s",
"time": 42
}
time is expressed in seconds.
Send Error Notification
- Endpoint:
/template/notify/error - Request Body:
{
"caller": "backup-job",
"message": "Error while moving file",
"critical": true,
"extra": [
{
"name": "Path",
"value": "/mnt/backups"
}
]
}
Health Probes
GETorHEAD /readyreturns200 Readyafter configuration and templates initialize.GETorHEAD /livereturns200 Alivewhile the process can serve HTTP.
Response Statuses
| Status | Meaning |
|---|---|
200 |
Notification delivered |
400 |
Invalid or missing request data |
404 |
Unknown route or template |
405 |
Unsupported method |
415 |
Content type is not JSON |
502 |
Discord rejected the request or delivery retries failed |
504 |
Discord delivery exceeded its timeout |
Haven Notify suppresses Discord mention parsing and safely truncates content to Discord's message and embed limits. Transport errors, rate limits, and server errors receive at most two retries within a ten-second total delivery budget.
Run
Go
WEBHOOK_URL=https://discord.com/api/webhooks/... go run .
Docker
docker build -t haven-notify .
docker run --rm -p 8080:8080 \
-e WEBHOOK_URL=https://discord.com/api/webhooks/... \
haven-notify
The runtime container uses a non-root haven-notify user and contains only the binary, Alpine runtime files, and CA certificates.
Tests
Run unit and HTTP contract tests:
go test ./...
Run static analysis, the race detector, and coverage (the race detector requires CGO support):
go vet ./...
go test -race -shuffle=on -count=1 -covermode=atomic -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
Run the seeded fuzz target:
go test -run=^$ -fuzz=FuzzErrorNotificationHandler -fuzztime=10s .
Run the black-box container suite:
go test -tags=e2e -run '^TestContainerE2E$' -count=1 -timeout=5m -v .
The end-to-end suite builds the production Dockerfile, starts Haven Notify and a disposable MockServer on an isolated network, and verifies the complete HTTP-to-Discord payload flow. It never calls a real Discord webhook. Configure DOCKER_HOST or the Testcontainers host settings when the Docker daemon is not available through the platform's default socket.
Deployment
The Kubernetes Deployment, Service, and Ingress manifest lives in the haven homelab repository at infra/haven-notify.yaml. This repository holds the service source, Dockerfile, tests, and Gitea Actions pipeline.
The production manifest maps the HAVEN_WEBHOOK_URL key from the discord-webhook secret into the container's WEBHOOK_URL environment variable.
