When a content slot reaches its scheduled time, Vlozi runs it through a reliable, queue-backed publishing pipeline. This page explains exactly what happens from alarm to post.
The pipeline
scheduledAt arrives
│
▼
SlotScheduler Durable Object alarm fires
│
▼
fireSlot() — one queue message per target
│
▼
Cloudflare Queue (content-publish-queue)
│
├──► runPublish() for each message
│ │
│ ├── Orphan check (slot/target/connection still exist?)
│ ├── Terminal state guard (already published/failed?)
│ ├── In-flight sentinel check (publishStartedAt set?)
│ ├── Token refresh (Twitter only; LinkedIn/Meta lazy check)
│ ├── Media URL resolution (via Media Service binding)
│ ├── Pre-flight validation (char limits, scopes)
│ ├── Set publishStartedAt (in-flight sentinel)
│ ├── Platform API call (publish post)
│ ├── Charge 1 credit (idempotent on targetId)
│ └── Update target status + transition slot
│
└──► Retry on failure (×3: +1m → +5m → +15m)
│
└──► Dead-letter queue (DLQ) after 3 failures
│
└── Mark target + slot as failedPer-target isolation
A slot's targets are enqueued as separate queue messages. If a slot targets Twitter and LinkedIn:
- Two messages go into the queue
- They are processed independently
- Twitter can succeed while LinkedIn fails and retries
- The slot's overall status reflects all targets:
- All published →
published - Some failed after retries →
partially_failed - All failed →
failed
- All published →
Retry policy
| Attempt | Delay before retry |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |
| After 3rd fail | Moved to dead-letter queue |
Retryable errors: rate limit (429), server error (5xx), network timeout.
Terminal errors (no retry): invalid token (401), permission denied (403), content policy violation.
The retry delay uses the platform's Retry-After header when provided. For Twitter rate limits, the actual reset time from x-rate-limit-reset is used.
At-least-once guarantee
The queue uses at-least-once delivery semantics. The engine guards against duplicate posts at each stage:
- Terminal state guard — if a target is already
publishedorfailed, the message is acknowledged and discarded - In-flight sentinel —
publishStartedAtis set before the platform API call. If the Worker restarts mid-call and a second delivery arrives, the sentinel marks the target asPUBLISH_INTERRUPTEDand acks. This prevents double-posting when a crash happens after the first API call went out.
Credits
Credits are charged after a successful platform API call, not before. The charge is idempotent — if a retry is attempted after a successful publish that the Worker didn't acknowledge, the credit ledger returns the same transaction without double-charging.
If your credit balance is insufficient at charge time, the target is marked INSUFFICIENT_CREDITS and the message is acknowledged (not retried). The slot fails. Top up your credits and create a new slot.
Dead-letter queue
After 3 failed retries, the message moves to the dead-letter queue (content-publish-dlq). The DLQ handler:
- Marks the target as
failedwith reasonDEAD_LETTERand the error detail - Transitions the slot to
failed(orpartially_failedif some targets published) - Acknowledges the DLQ message (no further retries)
NOTE
Credits for dead-lettered publishes are not automatically refunded. Contact support for manual reconciliation if this occurs.
What you can do after a failure
From the dashboard, open the failed slot and check the Activity log to see exactly which step failed and what error the platform returned.
Options:
- Retry manually — reset the target to
pendingand re-enqueue via the activity log - Edit and reschedule — change the slot to
failed → draft, fix the content, and re-submit - Delete — remove the slot if the content is no longer relevant
Publish logs
Every publish attempt is recorded in the publish_logs table. Each row captures:
- Action:
publish_attempt,publish_success,publish_failed,retry - Full platform response (JSON) for debugging
- Error code and message
View them at Content Engine → [slot] → Activity or via the API:
GET /content/activity?from=2026-07-01T00:00:00Z&to=2026-07-15T00:00:00Z