Delivery and guarantees
Syndroo is publishing infrastructure, so the important questions are about delivery: what exactly is stored, what has been confirmed, what may have happened, and what it will never do quietly. These pages describe the 0.2.0-rc.1 candidate.
Post and publication
Two records describe every request.
- A Post is one publishing request: shared content, the target platforms, optional per-platform overrides, and an optional schedule. Its status is an aggregate across every selected platform.
- A Publication is one Post's delivery to one platform, with its own content, attempt count, and outcome.
If you select three platforms, one Post owns three Publications. That is why the aggregate can be partial: some platforms succeeded and some failed, and the response records each one separately.
The delivery model
The Worker keeps a small, explicit pipeline.
- Admission. A Bearer-authenticated
POST /v1/postsvalidates the body and platform configuration, then writes the Post and one Publication per platform into Cloudflare D1. - Queue. Immediate work is sent to Cloudflare Queues. Queue delivery is at least once.
- Claim. A Queue consumer claims one Publication atomically before the outbound platform request. Publication state transitions and their Post aggregate update commit together in D1, and the claim read happens inside that transaction. A statement failure rolls the claim back.
- Publish. The selected adapter makes one bounded outbound request with application-controlled timeouts and retries.
- Record. The confirmed result, external identifier, and any error are stored on the Publication and reflected in the Post status.
- Recover. A Cron Trigger scans every 15 minutes for due posts and for stale jobs. If enqueue failed, or a Queue lease went stale, Cron acts as a small outbox recovery loop.
Because claiming happens before the outbound call, duplicate Queue messages do not normally duplicate posts. Cross-platform exactly-once delivery is not promised.
Idempotency
Client retries should send an Idempotency-Key. D1 stores a unique key so a deployment retry cannot create another logical post.
- Same key and same request body: the stored result is replayed with
replayed: trueand HTTP200. No second post is created. - Same key and different body: HTTP
409withIDEMPOTENCY_CONFLICT. - No key: a repeated request is a new Post.
Generate one stable key before you start retrying, and keep it with the original request body. That is what makes an automatic retry safe. Do not mint a fresh key on every attempt, because each key creates a separate Post.
Retries and backoff
Retries are narrow on purpose. Only rate-limit, provider-unavailable, and unambiguous network failures are retried, with a maximum of three application attempts.
- Minimum waits are 60 seconds after the first failure and 120 seconds after the second.
- The earliest retry time is persisted on the Publication, and both claim and Cron selection require that deadline. A duplicate Queue message cannot start the next attempt early.
- Migration
0003_retry_timing.sqladds the nullableretry_atcolumn. Apply migrations before deploying this Worker version. Rows written by older code keepretry_atasNULLand stay immediately eligible. - Cron runs every 15 minutes, so the minimum retry wait is not a delivery-time guarantee. If a Queue retry is lost, recovery waits for the existing 15-minute enqueue lease to expire and a later Cron scan.
Ambiguous outcomes
An ambiguous outcome means the platform may have accepted the content, but Syndroo cannot confirm whether it was published. A timeout, a connection loss, or a provider 5xx after the publish request can all produce this state.
- An ambiguous outcome is stored as publication
status: "failed"witherrorAmbiguous: true. It is not a separate status, and it is not a confirmed failure. - Ambiguous publications are never retried automatically. Blindly resending could duplicate a post that already exists.
- A Publication stuck in
publishingfor 15 minutes becomes an ambiguous failure instead of being replayed.
Check the platform directly before you resend anything. If the post exists, do not publish it again. Treating an unknown result as a confirmed failure is the one way to create a duplicate.
The candidate does not implement a manual retry endpoint or a reconciliation interface. Ambiguous entries are visible through GET /v1/posts/<id> with the information needed for manual verification.
Scheduling and timing
A future scheduledAt value creates a Post with status scheduled and Publications waiting for their scheduled time. Scheduling stays inside Syndroo; the platform's own queue is not used.
- Cron scans every 15 minutes, so scheduled publication can occur up to roughly 15 minutes after the requested time.
- A
scheduledAtvalue in the past is handled as an immediate post. - Maintenance mode does not pause Queue consumers or the Cron Trigger, so already accepted and scheduled work continues during a maintenance window.
What 0.2 does not include
The candidate is deliberately narrow. These are outside the 0.2 scope and must not be assumed from the API surface.
- No web management interface or dashboard. The API is the interface.
- No OAuth authorization flows or token renewal. You obtain and refresh platform credentials yourself.
- No media uploads. Every adapter publishes text.
- No multi-user or tenant model. One deployment serves one operator.
- No manual retry endpoint, per-request author selection, or audience targeting.
- No additional platform adapters during the 0.2 preparation, and no additional experimental feature flag. Configuring credentials opts into that platform.
Platform readiness
An officially supported platform has passed live validation for the release being assessed. Everything else is experimental, with its limits documented.
| Platform | 0.2.0-rc.1 status | Evidence |
|---|---|---|
| Bluesky | Release gate, not yet validated live | Official @atproto/api SDK path exercised by the local Mock SNS end-to-end gate. Live-account acceptance pending. |
| Threads | Release gate, not yet validated live | Native HTTP adapter exercised by the local Mock SNS end-to-end gate. Live-account acceptance pending. |
| X | Experimental | Implemented through the official @xdevplatform/xdk SDK and covered by unit tests. Not validated against a live account. |
| Tumblr | Experimental | Native HTTP with OAuth 1.0a and preflight text validation. Covered by unit tests. Not validated against a live account. |
| Experimental | Native HTTP with little-text escaping and header-based confirmation. Covered by unit tests. Not validated against a live account. |
The Mock SNS gate proves wiring, persistence, idempotency, ambiguity handling, and scheduling. It cannot prove provider permissions, API compatibility, or rate limits. Read Run the local test gates for the commands.