How scheduling works
Set a future publish time
In the post editor, toggle Schedule for later and pick a date and time. Set your timezone so the time is interpreted correctly — the
timezone field accepts any IANA timezone string (e.g. America/New_York, Europe/London).Post is enqueued
On save, the backend sets
status to SCHEDULED and stores the scheduledAt timestamp. It then calls Producer.queueSocialPost() for each selected platform, putting a PostQueueMessage into RabbitMQ with the target delivery time.Worker processes the job
The
PostWorker consumes messages from the queue. When the scheduled time arrives, it validates platform credentials, uploads any media, and calls the platform API. Each platform job runs independently.Queue architecture
Each platform gets its own message in the queue. ThePostQueueMessage payload carries everything the worker needs:
PostQueueMessage shape
Each
postId + platform pair is treated as an independent job. If you publish to three platforms, three separate queue messages are created and processed concurrently.Setting a scheduled time via API
IncludescheduledAt (ISO 8601) and timezone in the create or update request body:
POST /api/posts
201 response
The
scheduledAt value in the response is stored in UTC regardless of the timezone you supplied.Editing a scheduled post
Send aPUT request to /api/posts/:postId with the updated fields. The same schema used for creation is applied for validation. You can change the text, platforms, media, or reschedule to a new time.
PUT /api/posts/:postId
Cancelling a scheduled post
SendPOST /api/posts/:postId/cancel to cancel a post that has not yet been processed.
POST /api/posts/:postId/cancel
200 response
status field is updated to CANCELLED in the database. When the worker picks up the queued messages for this post, it detects post.status === "CANCELLED" and skips them immediately without calling any platform API.
Cancellation works by marking the database record. The messages already in RabbitMQ are not physically removed — the worker is responsible for checking post status before processing.
COMPLETED or already CANCELLED). Attempting to cancel a post that does not meet this condition returns a 404 error.
Retrying a failed post
If one or more platforms fail, you can retry by callingPOST /api/posts/:postId/retry. Only platforms with platformStatus.status === "failed" are re-enqueued.
POST /api/posts/:postId/retry
200 response
pending in the database:
Retry logic in post.controller.ts
Retried posts are delivered immediately regardless of the original
scheduledAt. They skip the scheduler and go straight to the front of the queue.Automatic retries for transient errors
ThePostWorker also handles retries internally for transient failures (network timeouts, rate limit responses). It checks whether an error is retryable and re-queues via the dead-letter exchange (DLX) with exponential backoff — up to 3 attempts per platform per job.
Retry logic in posting.worker.ts
failed and the message is acknowledged.
Post status transitions
| From | To | Trigger |
|---|---|---|
DRAFT | PENDING | User publishes the draft. |
DRAFT | SCHEDULED | User schedules the draft. |
PENDING | PROCESSING | Worker begins processing. |
SCHEDULED | PROCESSING | Scheduled time arrives, worker begins. |
PROCESSING | COMPLETED | All platforms succeed. |
PROCESSING | PARTIAL_SUCCESS | Some platforms succeed. |
PROCESSING | FAILED | All platforms fail. |
PENDING | CANCELLED | User cancels before worker picks it up. |
SCHEDULED | CANCELLED | User cancels before the scheduled time. |
FAILED | PENDING | User triggers a retry. |
The calendar view
The Calendar page at/calendar displays all your scheduled posts on a calendar interface, letting you see posting frequency and manage upcoming content at a glance.