{"protocol":"clank-doc/1","frameworkVersion":"0.8.0","slug":"services","title":"Service drivers","description":"Clank keeps external services behind explicit, inspectable drivers. Generated blueprints write src/service requirements.ts , which records every named service, kind, capability, and whether production startup should require it.","group":{"id":"full-stack","title":"Full stack"},"url":"https://docs.clank.run/docs/services","source":"docs/services.md","headings":["Files","Email","Durable jobs and cron","Webhooks","Production boundaries"],"tableOfContents":[{"id":"files","title":"Files","level":2},{"id":"email","title":"Email","level":2},{"id":"durable-jobs-and-cron","title":"Durable jobs and cron","level":2},{"id":"webhooks","title":"Webhooks","level":2},{"id":"production-boundaries","title":"Production boundaries","level":2}],"markdown":"# Service drivers\n\nClank keeps external services behind explicit, inspectable drivers. Generated blueprints write `src/service-requirements.ts`, which records every named service, kind, capability, and whether production startup should require it.\n\n```ts\nimport {\n  createServiceRegistry,\n  defineServiceDriver,\n  openJobQueue,\n  openLocalFileStore,\n} from \"@clank.run/framework/services\";\nimport { assertServices } from \"./service-requirements.ts\";\n\nconst files = await openLocalFileStore({\n  directory: \".data/files\",\n  signingKey: process.env.FILE_SIGNING_KEY!,\n});\n\nconst services = createServiceRegistry([\n  defineServiceDriver({\n    name: \"uploads\",\n    kind: \"files\",\n    capabilities: [\"signed-read\", \"signed-write\"],\n    service: files,\n  }),\n]);\n\nassertServices(services);\n```\n\nThe registry gives humans and agents one deterministic place to inspect configuration, validate blueprint requirements, run health checks, and close resources.\n\n## Files\n\n`openLocalFileStore` provides integrity-checked local object storage:\n\n- logical keys never become filesystem paths;\n- data and metadata are owner-only and written atomically;\n- every read verifies size and SHA-256;\n- upload size and content type are bounded;\n- signed capabilities bind one key, one operation, and one expiry; and\n- the built-in HTTP handler supports signed `PUT`, `GET`, and `HEAD`.\n\nThe `FileStore` interface is the compatibility target for a future remote object-storage driver. Application code should store file keys and metadata, not local paths.\n\n## Email\n\n`openFileEmailService` writes a development outbox without sending mail. `createHttpEmailService` sends a normalized JSON envelope to an HTTPS delivery service with timeouts, bounded retries, bearer credentials, and idempotency keys.\n\nEmail validation rejects header injection and reserved transport headers. Verification, recovery, and MFA callbacks from `defineAuth` can call either driver.\n\n## Durable jobs and cron\n\n`openJobQueue(database, handlers)` stores jobs in SQLite and implements:\n\n- JSON payload validation;\n- unique idempotency keys;\n- scheduled `runAt` execution;\n- transactional worker leases and expired-lease recovery;\n- handler timeouts and abort signals;\n- exponential bounded retries;\n- dead-letter state and explicit retry; and\n- multiple cooperating workers against the same database.\n\nCron schedules should enqueue jobs with stable unique keys such as `daily-report:2026-07-16`. The queue owns delivery state; business handlers remain ordinary async functions.\n\n## Webhooks\n\n`signWebhook` and `verifyWebhook` bind the exact body to a timestamped HMAC-SHA256 signature and enforce a replay window. `createWebhookSender` uses HTTPS, rejects redirects, preserves one delivery ID across retries, and retries only transient responses.\n\n## Production boundaries\n\nLocal files and a single SQLite job database are appropriate for a single-node deployment. Horizontal deployments must bind compatible remote object storage and a shared job/database topology. Service capabilities are part of the blueprint so the deployment platform can refuse an incomplete production plan instead of silently degrading.\n"}