Skip to content

Services

This page configures the optional services a deployment can add beside its databases: a cache, a search engine, webhooks, and one email service.

{
"connections": [
{ "name": "crm", "url": "postgres://[email protected]/crm" },
{ "name": "shared", "kind": "cache", "url": "valkeys://cache.internal:6379",
"auth": { "password": "${VALKEY_PASSWORD}" } },
{ "name": "search", "kind": "search", "url": "https://search.internal:8108",
"auth": { "apiKey": "${TYPESENSE_API_KEY}" } },
{ "name": "mail", "kind": "email", "url": "smtps://smtp.example.com:465", "from": "CRM <[email protected]>",
"auth": { "username": "${SMTP_USER}", "password": "${SMTP_PASSWORD}" } },
{ "name": "dealer-hooks", "kind": "webhook", "url": "https://hooks.dealer.example/tablewalk",
"auth": { "bearer": "${DEALER_HOOK_TOKEN}" } }
]
}

None is a source of truth, and a deployment that names none runs the same without them. They are not browsed or offered to agents. Credentials go in auth as ${VAR} references, never in the URL, and are never logged. Search and webhooks are preview.

A Valkey server, or anything that speaks its protocol (Redis included), shared by every instance of a deployment.

Field Meaning
url valkeys://host:6379 (TLS) or valkey://host:6379; rediss:// and redis:// too. /2 after the port picks a database.
auth { "password": "${VALKEY_PASSWORD}" }, plus "username" for an ACL user.
insecure true allows plain valkey:// / redis:// on a trusted network.
allowLinkLocal As on an HTTP connection.

It holds cached API answers for an HTTP connection with "cache": { "ttl": 60 }, and the rate-limit windows of public forms, so instances behind a load balancer spend one budget. When the cache does not answer, reads miss and rate limits fall back to this process’s own count; the outage is said on stderr at most once a minute.

A Typesense server.

Field Meaning
url The server’s base URL; https unless insecure.
auth { "apiKey": "${TYPESENSE_API_KEY}" }.
collectionPrefix Collections are <prefix>_<app>_<resource>; default tablewalk.
insecure, allowLinkLocal As on an HTTP connection.

An App names which resources are searched, and by which columns:

resources: {
contact: { access: 'write', search: { connection: 'search', fields: ['name', 'company', 'email'] } },
},

A contains filter on a searched field — the list’s search box, a reference picker, the palette — asks the engine for up to 250 keys, best first, then reads those rows from the database under the reader’s grants, so rows and counts agree. When the engine is down the filter runs on the database and the reader is told once.

The App re-indexes each record after its own writes. For writes made elsewhere, and after first declaring search, run tablewalk --app <dir> --config <file> --reindex [resource].

Not yet: tenant Apps, a secondary source, or a reference path such as owner_id.name. The resource needs a single-column primary key.

A webhook is where a command’s effects go after it commits: post('dealer-hooks', …). The App names it; the config says where it is.

  • https, or plain http only to localhost, 127.0.0.1 or [::1]. Redirects are refused and link-local addresses are never reached.
  • A credential in auth: { "bearer": "${TOKEN}" } or { "header": "x-signature", "value": "${SECRET}" }.

Each delivery is a POST of JSON:

{ "effect": "notifyDealer", "command": "convertLead", "receipt": "<receipt id>",
"occurredAt": "2026-09-13T12:00:00.000Z", "payload": { … } }

with an Idempotency-Key header that is the same on every attempt of one effect; deduplicate by it. A 2xx is delivered. Anything else is retried with backoff (1 s doubling to five minutes, eight attempts), then fails until an operator redrives it with redriveEventDeliveries from tablewalk/maintenance; readiness reports effects: work_failed.

Provision the delivery store once, with the server stopped: provisionSqliteEffects(path, { appId, commands }) or provisionPostgresEffects(url, { appId, commands }). A new command version or effect is a new destination, provisioned the same way.

The one email service a server sends through: SMTP over TLS, or log: to write each message instead. What it sends, and how a notice’s email waits for its write to commit: email.

examples/docker/docker-compose.yml starts the cache and the search engine under a services profile: docker compose --profile services up, with VALKEY_PASSWORD and TYPESENSE_API_KEY from your shell or an .env file.