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.
- Scheduler’s core trick is not scheduling, but forcing AI output into a structured workflow that downstream systems can trust.
- Zernio collapses a messy multi-platform integration problem into one smaller abstraction layer, which keeps the codebase lean.
- The reliability story lives in narrow loops, from cron-driven publishing to refresh-token rotation and replayable requests.
- The repo reads like a prototype with production instincts, especially in auth and session handling, even before it grows into a fuller product.
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.
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);
}
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 approach | Scheduler’s approach | Why it matters |
|---|---|---|
| Per-platform OAuth handlers | One Zernio abstraction | Fewer brittle integration paths |
| Freeform AI output | JSON-in-prompt structured output | Software can trust the shape of the result |
| Cloud media storage | Local generated assets | Easier self-hosting and simpler deployment |
| Manual session recovery | Refresh token rotation and replay | Users stay signed in without messy edge cases |
| Multi-service sprawl | One unified pipeline | Complexity 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 design | Scheduler’s auth design | Why it matters |
|---|---|---|
| Store refresh tokens in plain form | Hash refresh tokens before storing | A database leak is less damaging |
| Keep the same refresh token forever | Rotate refresh tokens on renewal | Replay attacks become much harder |
| Fail requests immediately on expiry | Queue, refresh, then replay requests | Users avoid unnecessary login prompts |
| Treat auth as a frontend concern | Split auth logic into explicit services | The 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.