Send operations

Drive the Send product (environments, settings, customers, endpoints, catalogs, messages, deliveries, replays) from the CLI, an MCP server, or native agent tools through one typed operation catalog.

Everything an agent can do to the Send product is one catalog of operations. The same catalog drives three surfaces, so they cannot drift:

  • repost send … — the clap CLI (--json prints the typed envelope).
  • repost mcp serve — a stdio MCP server whose tools/list is the catalog (send_*, schema_*, repost_*).
  • The native agent tools of the unified binary, registered from the same catalog.

Discover the catalog from the binary, never from a copy:

repost capabilities                        # "catalog" section: every operation with input/output JSON Schema
repost send --help                         # the command tree, generated from the catalog
repost send environments list --json       # the typed envelope

The envelope

Every operation answers repost.send.envelope/v1, on success and on failure:

{
  "schema": "repost.send.envelope/v1",
  "ok": true,
  "context": { "orgId": "org_…", "environmentId": "env_…" },
  "data": { "…": "…" },
  "page": { "nextCursor": null, "hasMore": false, "asOf": "2026-09-08T11:00:00Z", "consistency": "live_keyset" },
  "warnings": ["…"],
  "requestId": "…"
}

A failure keeps the same shape with ok: false and a typed error:

{ "ok": false, "error": { "code": "revision_conflict", "message": "environment changed since the revision you read", "retryable": false, "details": { "currentRevision": "rev1:1788865138569" } } }

Branch on error.code, never on the message. Codes include invalid_argument (with fieldErrors), not_found, forbidden, revision_conflict, head_conflict, idempotency_conflict, in_progress (retryable, with retryAfterMs), unsupported_filter, content_expired, content_not_captured, capability_unavailable, ambiguous_replay_source, artifact_too_large, quota_exceeded (a plan limit; not retryable) and internal. The CLI maps each code to a stable exit code.

Rules every operation follows

  • Name the environment explicitly. ENV accepts an ID, slug or exact name and is resolved once; an ambiguous name fails with candidates. Nothing is inferred from an earlier call.
  • Reads paginate with opaque cursors. Pass nextCursor back until hasMore is false; --all on the CLI walks every page and fails loudly on a partial export.
  • Writes carry the revision you read. Settings, customers, endpoints and environments return revision; a mutation must send expectedRevision, and a concurrent change (dashboard included) fails with revision_conflict instead of clobbering.
  • One-time results are idempotent by key. Creates that mint credentials, tracked replays and deployments take an idempotencyKey; the same key returns the original receipt and never mints or queues twice. A changed input with the same key is idempotency_conflict. Publishing is the exception: a publish key identifies the publish on its own for 10 minutes, so a changed body with the same key returns the first message's id and is discarded, never idempotency_conflict.
  • Secrets never reach the terminal or the model. Operations with effect: secret (environment publish key, API token, signing-secret reveal/rotate, custom headers, portal access link, one-use sample secret) require a protected destination: --secret-output PATH on the CLI (exclusive create, 0600, receipt in the envelope) or an operator-configured secretDestination id on the MCP server. A missing destination fails before anything is issued.

Delegated issuance

Account credentials mint outbound credentials only under a Send issuance grant attached when the CLI is paired ("Allow this CLI to issue Send credentials" in the pairing form). The grant bounds scopes, lifetime and whether environments may be created. Without it, send environments create and send tokens create answer capability_unavailable with reason no_issuance_grant; the fix is to pair again with the option enabled or mint the token in the dashboard.

Running the MCP server

export REPOST_SECRET_DESTINATIONS="vault=/absolute/dir/owned/by/you"   # the only places a one-time secret may be written
repost mcp serve                                                       # stdio transport

Secret-emitting tools take secretDestination: "vault" and answer a receipt (destination, delivered, recoverable: false, fields). Local adapters (repost_org_*, repost_auth_token_set from a file inside a destination, the schema_* workspace tools with an absolute root, and repost_update, which installs the verified release in place or only discovers one with check: true) run in the server process; lifecycle adapters that need a browser or an interactive terminal (repost auth login, repost forward) report capability_unavailable with the command that owns them.

A first session

repost send environments create "Payments" --idempotency-key env-payments-1 --secret-output ./payments.publish-key.json
repost send customers upsert payments acme --name "Acme" --metadata '{"tier":"gold"}'
repost send endpoints create payments acme --url https://hooks.acme.example/repost --subscriptions "order.created,order.failed"
repost send messages publish payments acme order.created --data '{"id":1}' --idempotency-key order-1
repost send messages deliveries payments msg_…            # every generation, superseded ones included
repost send deliveries attempts payments dl_…             # headers, body reference, latency, error class
repost send deliveries replay payments dl_… --idempotency-key replay-1
repost send operations wait op_… --target delivery        # the new generation and its actual outcome

Subscriptions are exact event names or the single wildcard *; patterns such as order.* are stored but match nothing. Retry policy overrides follow one closed contract ({"version":1,"delaysMs":[…]}) and every endpoint read reports which runtime cohort enforces them.