Scheduler: The AI-to-API Pipeline Behind a Smarter Social Media Stack

How a small TypeScript app turns Gemini output, refresh-token rotation, and multi-platform publishing into one deterministic workflow.

7 min read • View on GitHub • More from developedbyrd

A conveyor-like editorial scene shows a prompt card entering one side, being compressed into a structured JSON block in the center, and exiting as a scheduled post envelope with small platform badges trailing behind it. A clock and a shield sit in the background to suggest cron timing and token security. This image explains that Scheduler is a transformation pipeline, not just a calendar UI.
Scheduler turns messy inputs into a narrow, dependable pipeline: generate, structure, schedule, publish.
Key Takeaways

Scheduler looks like a social media tool. The more interesting truth is that it behaves like a production pipeline: one step turns a prompt into structured content, another turns that content into an image-ready artifact, and a later loop publishes it when the schedule says so. That is a different species of software from a dashboard with a calendar attached.

The real product is a pipeline

That shape matters because it reduces uncertainty. AI is unpredictable, OAuth is brittle, and publishing across platforms is where little apps usually unravel. Scheduler narrows each problem until it can be handled by a small set of explicit services.

One prompt becomes a scheduled post because every handoff is constrained, typed, and replayable.

Why forcing Gemini to speak JSON is the key move

The strongest design choice in the repo is also the least glamorous. Instead of asking Gemini to write a post in freeform prose, Scheduler asks for structured output with fields like content and imagePrompt. That makes the model’s answer usable by software, not just readable by humans.

const prompt = `Return JSON with content and imagePrompt only`;

const result = await gemini.models.generateContent({
  model: 'gemini-3.1-flash',
  contents: prompt,
});

const parsed = JSON.parse(result.text);

const post = {
  content: parsed.content,
  imagePrompt: parsed.imagePrompt,
};

if (generateImage) {
  const image = await gemini.models.generateImage({
    model: 'gemini-3.1-flash-image',
    prompt: parsed.imagePrompt,
  });

  await saveLocalAsset(image);
}
A close-up shows a sealed refresh token being stamped, rotated, and replaced in a drawer labeled active session. To the left, a small request queue waits paused, and on the right it resumes in order after the token swap completes. This image explains how the app keeps authentication stable while replaying failed requests.
The auth flow is built to recover cleanly, not just to log users in once.

Zernio is the quiet superpower

This is where the repo gets unusually efficient. Instead of custom OAuth handlers and per-network API wrappers for every platform, Scheduler leans on Zernio as a normalization layer. The result is a smaller codebase with less surface area to maintain.

Traditional approachScheduler’s approachWhy it matters
Per-platform OAuth handlersOne Zernio abstractionFewer brittle integration paths
Freeform AI outputJSON-in-prompt structured outputSoftware can trust the shape of the result
Cloud media storageLocal generated assetsEasier self-hosting and simpler deployment
Manual session recoveryRefresh token rotation and replayUsers stay signed in without messy edge cases
Multi-service sprawlOne unified pipelineComplexity is concentrated instead of scattered

The important distinction is not that Scheduler removes complexity. It relocates it. The hard parts sit in a few obvious places, which is exactly where you want them if a small team is going to keep the system healthy.

The publishing engine is simple on purpose

The cron job is blunt and effective. Every minute it scans for posts that are due, resolves the relevant accounts, publishes through the integration layer, and marks failures locally if something goes wrong. There is no drama in that loop, which is the point.

cron.schedule('* * * * *', async () => {
  const duePosts = await Posts.find({
    status: 'scheduled',
    scheduledFor: { $lte: new Date() },
  });

  for (const post of duePosts) {
    try {
      await zernio.posts.createPost({
        publishNow: true,
        media: post.mediaUrls,
        content: post.content,
      });

      post.status = 'published';
      await post.save();
    } catch (error) {
      post.status = 'failed';
      post.failureReason = String(error);
      await post.save();
    }
  }
});

The auth flow is more serious than the UI suggests

This repo’s most production-minded work is hiding in session management. Access tokens stay short-lived, refresh tokens are hashed before storage, and rotation reduces replay risk when a token is used again. That is stronger discipline than many prototypes ever reach.

Naive session designScheduler’s auth designWhy it matters
Store refresh tokens in plain formHash refresh tokens before storingA database leak is less damaging
Keep the same refresh token foreverRotate refresh tokens on renewalReplay attacks become much harder
Fail requests immediately on expiryQueue, refresh, then replay requestsUsers avoid unnecessary login prompts
Treat auth as a frontend concernSplit auth logic into explicit servicesThe system is easier to reason about

The frontend interceptor pattern is also worth noting. If a request hits a 401, the app can pause outgoing traffic, refresh the session, and replay what failed. That is the kind of small UX detail that makes a rough prototype feel much more finished than it really is.

What it would take to scale this cleanly

Scheduler already shows good instincts, but the edges are still prototype-shaped. Local media storage is practical, yet it will become a constraint if the system needs to scale horizontally. The absence of an obvious test suite or CI also suggests a project still optimizing for speed of iteration over operational armor.

That does not weaken the design lesson. It sharpens it. The repo shows how far a solo builder can get when they concentrate complexity into a few deliberate seams: structured AI output, a normalized social layer, and a narrow publish loop.