When we set out to build Unipostudio, the assumption — ours, and most of the early team's — was that scheduling was the hard part. The visual queue, the time picker, the per-platform preview. That's what a user sees. That's what a competitor's screenshots show. That's what shapes the marketing site.
The user sees the easy part.
The hard part — and it is hard in a way that doesn't show up in a demo — is the slow, careful machinery underneath that decides when to actually call the platform API, how to handle a rejection, what to do when the user's auth token has rotated, what to do when the platform changes its rate-limit policy without telling anyone, and what to do when the post would have published thirty seconds ago but the worker that owned it just died.
A few things that don't show up in screenshots, but that we spend most of our time on.
A scheduled time is not a publishing time. The user sets "Tuesday, 9:00 AM IST." The actual publishing time is some moment between that and a few minutes after, when the worker picks the job off the queue, validates the token, validates the asset, and makes the call. We document this in the UI, but we also build for it: every job carries a target time, a window, and an expiry. If the worker can't publish before expiry, the job moves to a failed state with a real reason. The user finds out by email, not by going to the platform tomorrow and discovering nothing happened.
Rate limits are not numbers. They are policies that change. Every social platform has rate limits, and every social platform changes them on a Tuesday morning with a one-line note in their developer changelog. Our scheduler treats published rate limits as defaults, not truth. The truth is what the platform's response headers say right now. We back off based on the actual response, not on what the docs said three months ago. This sounds obvious. It is the source of approximately half of the bugs new social-tool teams have to learn the hard way.
Tokens are state, not credentials. We store the refresh window, the last-known scope, the user's reconnect history. When a token rotation fails — and they do, on the platforms that quietly rotate every few months — the user gets a notification before their next scheduled post fails, not after. We send the warning at twenty-four hours, twelve hours, and one hour. The one-hour warning has the highest reconnect rate. We don't know exactly why, but we suspect urgency.
Idempotency is non-negotiable. Every publish call is keyed by a job ID we generate before the call goes out. If the network blips between the API accepting the post and us recording that it accepted, we don't republish. We ask the platform what the status of this job ID is. Every platform supports this in some form. Some require you to dig. Worth digging.
The worker that owns a job has to be able to die at any moment. The scheduler is, structurally, a leasing system. A worker takes a lease on a job, the lease has an expiry, and if the worker doesn't extend the lease (because it crashed, was killed, lost its network), another worker can pick the job up. This is how we don't end up with the disaster scenario — the same post published twice because two workers thought they owned it.
Failures are first-class data. Every failed publish becomes a record with a structured reason, a recovery action, and an estimate of how confident we are the action will work. The user sees this. We don't hide it behind a generic "something went wrong" toast. If LinkedIn rate-limited us, we say so. If the asset was over the platform's size limit, we say so, and we offer to resize and reschedule. Failures with good copy convert into successes more often than failures with bad copy.
None of this is in the user's mental model of the product. None of it should be. If we do our job, the user sets a time, the post goes out, and the only thing they notice is that nothing went wrong. That is the actual deliverable. The scheduler is the easy part.
UnipostudioSchedulersReliabilityAPIs
Have something ambitious in mind?
We reply to every email within 48 hours. Call or async, whichever you prefer.