New standalone service that receives authenticated HTTP POSTs (e.g. from a cronjob elsewhere on the home network) and posts them to a Discord channel via an Incoming Webhook. Fire-and-forget, no dependency on discord-bot's bot session at all - mirrors alexa-bridge's precedent of a small bridge service with its own HTTP listener and auth rather than adding an inbound edge to an existing internal service. Wires alert-bridge into go.work, CI (changes filter, test job, new build-alert-bridge job), and the other five Dockerfiles' COPY lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
alert-bridge
alert-bridge turns an authenticated HTTP POST from an external caller (e.g.
a cronjob running elsewhere on the home network) into a message posted to a
Discord channel. It is fire-and-forget: each request becomes one new Discord
message, there is no editing or reacting to prior messages.
Unlike ha-gateway/ai-gateway/discord-bot/tts-gateway, it has no
dependency on any other service in this repo — it posts directly to a Discord
Incoming Webhook, not
through discord-bot's bot session. That keeps the whole flow to one hop and
means discord-bot needed zero changes to support this feature.
Runtime Flow
- The process loads
.env, configures logging/telemetry, and starts serving HTTP onHTTP_PORT. - A caller
POSTs to/alertswith a bearer token and a JSON body. - The handler checks the bearer token (constant-time compare against
API_KEY), validates the body, and defaultsleveltoinfoif omitted. - The alert is formatted as a Discord embed (color-coded by level) and POSTed
to
DISCORD_WEBHOOK_URL.level=erroralerts additionally prefix the message with a mention ofMENTION_USER_ID, since embeds alone never trigger a Discord ping.
HTTP API
-
POST /alerts— requiresAuthorization: Bearer <API_KEY>.{"source": "ba-cronjob", "message": "Finished with 0 errors", "level": "info"}source,message: required.level: optional, one ofinfo|warn|error, defaults toinfo.- Responses:
202delivered,400bad request,401bad/missing token,502the Discord webhook call itself failed.
-
GET /healthz: unauthenticated liveness/readiness check, always200 OK.
Configuration
Environment variables:
| Variable | Default | Description |
|---|---|---|
HTTP_PORT |
8080 |
HTTP listen port |
API_KEY |
required | Shared secret callers must present as Authorization: Bearer <API_KEY> |
DISCORD_WEBHOOK_URL |
required | Target channel's Discord Incoming Webhook URL |
MENTION_USER_ID |
empty | Discord user ID mentioned on level=error alerts; leave empty to never mention |
OTEL_ENDPOINT |
empty | OTLP gRPC collector endpoint; empty disables telemetry |
LOG_LEVEL |
info |
debug, info, warn, or error |
LOG_FORMAT |
json |
json or text |
Example env file: .env.example
Local Run
cd alert-bridge
cp .env.example .env
# edit .env: set API_KEY and DISCORD_WEBHOOK_URL
go run ./cmd/bridge
curl -X POST http://localhost:8080/alerts \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"source":"ba-cronjob","message":"Started","level":"info"}'
Test And Build
go test ./...
go build ./...
Build the container image from the workspace root:
docker build -f alert-bridge/Dockerfile -t alert-bridge:dev .
Package Map
cmd/bridge/ # process entrypoint and wiring
internal/adapters/primary/http/ # POST /alerts handler: auth, decode, validate
internal/adapters/secondary/discordwebhook/ # builds the embed payload, posts to the Discord webhook
internal/app/ # thin orchestration between the HTTP handler and the Notifier
internal/core/domain/ # Alert{Source, Message, Level}
internal/core/ports/driven/ # Notifier interface
internal/config/ # environment loading
internal/logger/ # slog setup
internal/telemetry/ # OpenTelemetry setup
Deployment
Kubernetes manifests (Deployment/Service, and an internal-CA IngressRoute at
a *.home.arpa hostname so LAN callers outside the cluster can reach it) live
in the separate homelab repo, not here — not yet added as of this writing.
Limitations
- Single channel/webhook only — no per-source routing to multiple channels.
- No rate limiting of its own; relies on Discord's own webhook rate limits (5 requests/2s per webhook), which is more than enough for cronjob-scale notification volume.
- Fire-and-forget only: no message editing, threading, or reaction handling.
That would require routing through
discord-bot's live bot session instead of a plain webhook — not needed for the current use case.