Skip to navigation

Delivery and retries

Keep a durable outbox on your server. Key each request by environment, record_type, record_id and revision, and store the complete body before sending. Test and live delivery state must stay separate.

For a relational app, the core storage can be as small as this. The exact SQL types and locking syntax depend on your database:

CREATE TABLE ediphai_outbox (
environment TEXT NOT NULL,
record_type TEXT NOT NULL,
record_id TEXT NOT NULL,
revision BIGINT NOT NULL,
request_json JSONB NOT NULL,
state TEXT NOT NULL DEFAULT 'pending',
next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
attempts INTEGER NOT NULL DEFAULT 0,
receipt_json JSONB,
PRIMARY KEY (environment, record_type, record_id, revision)
);

Insert the outbox row in the same transaction that saves the source record. A worker claims due rows with a database lock, sends the stored body unchanged and saves the receipt. A test receipt never marks a live row sent.

Handle a send

  • Accepted — HTTP 202, outcome: "accepted": Store the durable receipt. Check status later if you need processing progress.
  • Duplicate — HTTP 200, outcome: "duplicate": Store the receipt; the same type, ID, revision and body was already received.
  • Revision conflict — HTTP 409, rule: "revision_conflict": Investigate the changed body; a correction needs a higher revision.
  • Deleted record — HTTP 409, rule: "deleted": Stop. No revision of a deleted record ID is accepted.
  • Invalid request — HTTP 422 or another non-retryable problem: Use its rule, JSON pointer and fix to correct the request before sending again.
  • Retryable response — HTTP 429, 503 or retryable: true: Retry the unchanged request with backoff. Honor Retry-After or retry_after_ms when present.
  • Timeout or lost connection: The API may have accepted the request. Retry the same body, ID and revision.

record_id stays stable for one source record. Raise revision when that record changes; a newer revision supersedes the earlier one. Do not create a new ID to hide a conflict. Set an HTTP timeout, but never treat it as proof that the API did not receive the request.

Read responses leniently. Ignore fields you don’t know, and treat an unfamiliar receipt state or error rule as nonterminal while you check the current docs. Don’t validate API responses against a strict frozen schema. Ediphai may add response fields without changing /v1.

Recover missing receipts

Use GET /v1/records/{record_type}/{record_id} to check a known record. GET /v1/records?since=... lists each record at its current revision, including deleted records. Follow every next_cursor with the same since value until it is null.

For a complete recovery scan, choose a since time before the first submission. Do not advance it from a row’s received_at: a later deletion appears with the prior revision’s receipt time. Deletion hides the record from standard Ediphai reads, and a later revision of that ID is refused. See the sender self-test for the receipt and reconciliation shapes.