Quick Start
The echo channel runs every part of Herald without a provider account: the webhook,
routing, the Nexus run, the outbox and dispatch. It does no authentication at all, so it only
exists behind --enable-echo and must never reach an internet-facing deployment.
You need a running Nexus with a published agent and a tenant
API key. In the examples the agent is router.
Start Herald
Section titled “Start Herald”PostgreSQL 17 plus Herald, with development credentials. The admin token, the encryption
key and the tenant-1 send key have committed defaults; override them anywhere real.
git clone https://github.com/monaccode/astromesh-herald.gitcd astromesh-heraldHERALD_ENABLE_ECHO=true docker compose -f deploy/docker-compose.yaml up --build
curl localhost:8081/healthzThe compose stack has no Nexus. NEXUS_URL defaults to http://host.docker.internal:8080,
a Nexus running on the host.
| Default | Value |
|---|---|
| Admin token | 0123456789abcdef repeated to 64 characters |
| Send key | tenant-1:dev-send-key |
Go 1.25+ and PostgreSQL 17.
createdb heraldmake build # bin/herald (the console is a placeholder until `make ui-build`)
export HERALD_ADMIN_TOKEN="$(openssl rand -hex 32)"DATABASE_URL="postgres://localhost/herald?sslmode=disable" \NEXUS_URL="http://localhost:8080" \HERALD_ENC_KEY="$(openssl rand -hex 32)" \HERALD_SEND_KEYS="tenant-1:$(openssl rand -hex 16)" \./bin/herald --enable-echoSend a message through the pipeline
Section titled “Send a message through the pipeline”-
Create an echo channel account.
Terminal window curl -X POST localhost:8081/admin/accounts \-H "X-Admin-Token: $HERALD_ADMIN_TOKEN" -H "Content-Type: application/json" \-d '{"tenant_id":"tenant-1","channel":"echo","external_id":"echo-1","credentials":{}}'# → 201 {"id":"<account id>", …} -
Bind it to an entry agent. Herald checks the agent against Nexus with the key in
configand rejects an unknown one with422, so a typo fails here and not in front of a customer.Terminal window curl -X POST localhost:8081/admin/bindings \-H "X-Admin-Token: $HERALD_ADMIN_TOKEN" -H "Content-Type: application/json" \-d '{"tenant_id":"tenant-1","channel":"echo","account_id":"<account id>","entry_agent":"router","fallback_message":"Out of service, try again later","config":{"nexus_api_key":"<tenant Nexus API key>"}}' -
Deliver an inbound message, as a provider’s webhook would.
Terminal window curl -X POST localhost:8081/webhooks/echo \-H "Content-Type: application/json" \-d '{"message_id":"m-1","account_id":"echo-1","sender":"+5491100000001","text":"hola"}'# → 200 {"queued":1}The
200comes back before the agent runs. A worker then resolves the account and tenant, runsrouterthrough Nexus, opens the conversation (unless the agent’sdata.routedeclines) and queues the answer. -
See the conversation and the reply.
Terminal window curl "localhost:8081/admin/conversations?tenant=tenant-1" -H "X-Admin-Token: $HERALD_ADMIN_TOKEN"curl "localhost:8081/admin/outbox?tenant=tenant-1" -H "X-Admin-Token: $HERALD_ADMIN_TOKEN"The reply’s row is
sentwithin a second. Echo delivers into memory, so nothing leaves the process. -
Send a proactive message.
Terminal window curl -X POST localhost:8081/api/v1/messages/send \-H "X-Herald-Key: <tenant-1 send key>" -H "Content-Type: application/json" \-d '{"channel":"echo","recipient":"+5491100000001","text":"recordatorio de pago"}'# → 202 {"message_id":"…","status":"pending"} -
Check that it went out.
202only meant queued.Terminal window curl localhost:8081/api/v1/messages/<message_id> -H "X-Herald-Key: <tenant-1 send key>"# → {"message_id":"…","status":"sent","attempts":0,"last_error":""}
If the agent fails, step 4 shows the fallback message queued instead of an answer, and Herald’s log says why.
Open the console
Section titled “Open the console”http://localhost:8081/ui/Sign in by pasting the admin token. The outbox screen shows each row’s retry budget, which is where a delivery that keeps failing becomes visible.
Next steps
Section titled “Next steps”- WhatsApp — a real channel, or the emulator if you have no Meta account yet
- Building Agents for Herald — what
routerreceives and how it can route - API Reference — every route