Skip to content

Operations

Every flag takes its default from an environment variable. herald --help prints the list.

FlagEnvironmentDefaultNotes
--database-urlDATABASE_URL—Required
--nexus-urlNEXUS_URL—Required. Every inbound message runs an agent behind it
--enc-keyHERALD_ENC_KEY—Required. Seals channel credentials; create once, never rotate
--admin-tokenHERALD_ADMIN_TOKENemptyEmpty: no /admin. Set: at least 32 bytes
--send-keysHERALD_SEND_KEYSemptytenant:key,tenant:key. Empty: no tenant send API. A malformed or repeated pair stops the boot
--trusted-sender-keyHERALD_TRUSTED_SENDER_KEYemptyEmpty: no send-on-behalf or identity lookup. Set: at least 32 bytes
--addr—:8081
--outbox-poll-interval—500msMust stay below the 1.5 s pause between fragments
--agent-run-timeoutHERALD_AGENT_RUN_TIMEOUT120sOne whole agent turn. An unparseable value falls back to the default
--public-base-urlHERALD_PUBLIC_BASE_URLemptyHerald’s public root, for channels that register their webhook (Telegram)
--whatsapp-app-secretWHATSAPP_APP_SECRETemptyWith the verify token, turns WhatsApp on
--whatsapp-verify-tokenWHATSAPP_VERIFY_TOKENempty
--whatsapp-graph-base-urlWHATSAPP_GRAPH_BASE_URL…/v21.0
--whatsapp-emulated-numberHERALD_WHATSAPP_EMULATED_NUMBERemptyComma-separated ids to emulate
--telegram-webhook-secretTELEGRAM_WEBHOOK_SECRETemptyTurns Telegram on
--telegram-api-base-urlTELEGRAM_API_BASE_URLhttps://api.telegram.org
--instagram-app-secretHERALD_INSTAGRAM_APP_SECRETemptyWith the verify token, turns Instagram on
--instagram-verify-tokenHERALD_INSTAGRAM_VERIFY_TOKENempty
--instagram-graph-base-urlHERALD_INSTAGRAM_GRAPH_BASE_URL…/v21.0
--instagram-emulated-accountHERALD_INSTAGRAM_EMULATED_ACCOUNTemptyComma-separated ids to emulate
--enable-echoHERALD_ENABLE_ECHOfalseUnauthenticated development channel
--enable-webchatHERALD_ENABLE_WEBCHATfalseWeb chat channel and its admin routes
--uiHERALD_UItrueEmbedded console at /ui/

Boolean variables only count as on with true or 1. Anything else, including a typo, is off, because echo and web chat open routes that run agents.

Herald treats a missing setting as a decision. Before debugging a 404, check this table.

MissingEffect
admin-tokenNo /admin, and the console has nothing to read
send-keysNo /api/v1/messages/send or status route
trusted-sender-keyNo send-on-behalf: agents’ send_message calls fail at Nexus
Both WhatsApp secrets/webhooks/whatsapp is 404; other channels still work
One WhatsApp or Instagram secretThe process exits at boot
telegram-webhook-secret/webhooks/telegram is 404
public-base-urlCreating a Telegram account is refused
enc-key, database-url, nexus-urlThe process does not start

A React console embedded in the binary, served at /ui/ (/ redirects there). Its files carry no data; everything it shows comes from the token-protected /admin API, so signing in means pasting the admin token. A tenant filter and a time window apply across screens.

ScreenShows
OverviewMessages per day and per channel, active conversations, outbox by status
ConversationsConversations by latest activity, and each one’s message thread
OutboxEvery delivery with its status, attempts out of 10 and last error; the operator test send
BindingsRouting rules: create, edit (config is merged), delete; webhook-only bindings without an agent
AccountsChannel accounts with masked credentials: create, delete

Kustomize base plus two overlays, each an ArgoCD Application with an Image Updater. There is no production overlay yet.

OverlayNamespaceImageNotes
devherald-dev:dev, rebuilt on every push to developWhatsApp and Instagram emulators on, web chat on, run timeout 300 s
mvpherald-mvp:mvp and immutable :X.Y.Z, built when the mvp tag movesWhatsApp emulator on, web chat on

Both include an in-cluster PostgreSQL component. The image is multi-stage (node for the console, go, then alpine) and runs as non-root.

How a change reaches a cluster. On dev, a push to develop publishes :dev; Image Updater sees the new digest and pins it into the Application, and ArgoCD rolls out. Nothing is committed back to git. On mvp, /deploy-mvp merges develop into main and moves the mvp tag. CI publishes :mvp and :X.Y.Z, and the same mechanism rolls it out. The tag is the gate: until it moves, mvp stays where it was. Use /deploy-dev to cut a release and /deploy-mvp to promote it.

Terminal window
deploy/bootstrap.sh dev # or mvp

It creates four Secrets outside git, then applies the Application and the Image Updater:

SecretContents
herald-postgresPostgreSQL user and password
herald-dbdatabase-url, pointing at the in-namespace PostgreSQL
herald-secretsenc-key, admin-token, send-keys, trusted-sender-key. Channel secrets (WhatsApp, Telegram, Instagram) are patched in afterwards
ghcr-pullRegistry credentials for the private image

The script is idempotent and never modifies herald-secrets once it exists, because regenerating enc-key would strand every stored channel credential. It prints the admin token once, only when it created it. To read it later:

Terminal window
kubectl -n herald-dev get secret herald-secrets -o jsonpath='{.data.admin-token}' | base64 -d; echo
Terminal window
make verify-manifests # renders both overlays and checks every flag exists in the binary

The Lint workflow runs it too, so a broken overlay fails CI instead of kubectl apply.

  • Herald must be reachable from the internet. Meta and Telegram deliver over HTTPS only, so the Ingress host and its certificate are required. Changing the host means updating Meta’s callback URL and re-registering Telegram bots.
  • Nexus lives in another namespace. NEXUS_URL must be the cross-namespace name (http://nexus.nexus-dev.svc.cluster.local:8080). A Herald that cannot reach Nexus still accepts webhooks and answers every one with the fallback message.
  • Emulators on an environment with real traffic. mvp turns the WhatsApp emulator on to validate flows before production. Clear it the day mvp takes real customers.
  • Health is liveness only. Both probes use /healthz, which says the process serves HTTP, not that PostgreSQL or Nexus are reachable.
  • manifestTargets in the Image Updater manifests is required. Without it the updater reports success every cycle while the Deployment keeps the mutable tag.
Terminal window
make build # bin/herald
make ui-build # builds the console into internal/webui/dist
make test # unit tests; PostgreSQL tests run when HERALD_TEST_DATABASE_URL is set
make lint # golangci-lint, pinned into bin/
make docker-build # astromesh-herald:<VERSION>

The end-to-end tests run the whole inbound pipeline against the echo channel, a fake Nexus and an in-memory store. The WhatsApp adapter’s tests replay Meta’s own webhook examples. The forwarding test uses the real WhatsApp adapter and the real webhook client against fake Graph and consumer servers that recompute the signature independently.

  • Agents receive text only: inbound media reaches them as a description, not as a file.
  • One media attachment per outbound message.
  • Templates on WhatsApp only, with body parameters only.
  • reply_to on Telegram only.
  • No metrics endpoint and no OpenTelemetry. Logs are structured JSON on stdout.
  • SMTP, push notifications and SMS are not channels.