Postiz is a self-hosted social media scheduler that covers 35+ platforms — X, LinkedIn, Bluesky, Reddit, Instagram, TikTok, YouTube, Discord, Mastodon, Nostr, and a dozen more. You connect your accounts, compose posts in a calendar UI, and the backend handles scheduling, retries, token refresh, and cross-platform publishing.
Why I starred it
Most open-source social scheduling tools die in one of two ways: they're abandoned mid-implementation, or they have no serious solution to durable job execution. Posting to 35 platforms at a scheduled time, handling rate limits, refreshing expired OAuth tokens, and retrying failed jobs — that's the hard part. Postiz chose Temporal as the job engine, which is an unusual and deliberate call that's worth looking at.
The platform count is also real. I scrolled through libraries/nestjs-libraries/src/integrations/social/ and counted 35 provider files: bluesky.provider.ts, farcaster.provider.ts, nostr.provider.ts, lemmy.provider.ts — platforms that most hosted tools don't touch.
How it works
The stack is a pnpm monorepo with a NestJS backend, a Next.js frontend, Prisma/PostgreSQL for persistence, Redis, and Temporal for workflow orchestration. Each social platform is a class that implements the SocialProvider interface defined in social.integrations.interface.ts.
That interface is the core abstraction:
// libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts
export interface SocialProvider extends IAuthenticator, ISocialMediaIntegration {
identifier: string;
maxLength: (additionalSettings?: any) => number;
editor: 'none' | 'normal' | 'markdown' | 'html';
isBetweenSteps: boolean;
scopes: string[];
refreshCron?: boolean;
convertToJPEG?: boolean;
// ...
}
Every provider implements post(), authenticate(), generateAuthUrl(), and refreshToken(). The integration.manager.ts instantiates all of them into a flat array at startup and exposes a lookup by identifier. Adding a new platform means implementing the interface and dropping the class into that array — nothing else to wire.
The scheduling backbone is interesting. When a post is saved, posts.service.ts calls startWorkflow():
// libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
async startWorkflow(taskQueue: string, postId: string, orgId: string, state: State) {
const workflows = this._temporalService.client
.getRawClient()
?.workflow.list({
query: `postId="${postId}" AND ExecutionStatus="Running"`,
});
for await (const executionInfo of workflows) {
// cancel any existing workflow for this post before re-scheduling
const workflow = await this._temporalService.client.getWorkflowHandle(
executionInfo.workflowId
);
if ((await workflow.describe()).status.name !== 'TERMINATED') {
await workflow.cancel();
}
}
// start new workflow...
}
Temporal handles what most scheduling tools have to implement manually: durable execution, retries with backoff, and the ability to cancel or reschedule an in-flight job. If the server crashes between scheduling and posting time, Temporal replays the workflow. The infinite.workflow.register.ts module also kicks off a persistent missingPostWorkflow on startup to catch anything that slipped through.
Error handling in the base SocialAbstract class is worth noting. The handleErrors() method returns typed error categories — refresh-token, bad-body, or retry — and the runInConcurrent() wrapper converts those into RefreshToken or BadBody exceptions that extend Temporal's ApplicationFailure. So a stale OAuth token automatically triggers a refresh workflow rather than just logging a failed job.
The AI layer sits in libraries/nestjs-libraries/src/agent/agent.graph.service.ts, built with LangGraph. When you ask it to generate posts, it runs a multi-step graph: pick a category, pick a topic, generate a hook, then write content — optionally with a Tavily web search for fresh context and DALL-E 3 for images. Each step is a structured LLM call with Zod validation on the output.
Using it
Docker Compose is the quickest path:
# docker-compose.yaml ships in the repo
docker compose up -d
# or self-build with the dev container
pnpm install
pnpm dev
The environment file is long — you'll need API keys for every platform you want to post to. The docker-compose.yaml ships pre-wired for Temporal, PostgreSQL, and Redis as separate services, so the infra is handled. For production you'll need to configure STORAGE_PROVIDER (local or Cloudflare R2), RESEND_API_KEY for email, and OPENAI_API_KEY if you want AI generation.
The public API is documented and there are first-party integrations for n8n, Make.com, and a Node SDK on npm (@postiz/node). If you want to push posts programmatically without touching the UI, the API is the right path.
Rough edges
No tests. I searched the entire repository for *.spec.* and *.test.* files and found nothing. For a project this size that's a risk — especially in the provider implementations where behavior varies per platform.
The setup is genuinely complex. You're running five services: Postiz app, PostgreSQL, Redis, Temporal, and Temporal's own UI. The docker-compose.yaml handles it, but if anything misbehaves you need to understand Temporal's concepts (task queues, workflow IDs, activity retries) to debug it.
OAuth setup is per-platform and requires registering your own app credentials on each network. For X this now costs money. The docs at docs.postiz.com cover the steps, but the number of API registrations is real overhead for a personal instance.
The AGPL-3.0 license means any hosted fork has to open-source its modifications. That's intentional — it's how they protect the business — but worth knowing if you're building on top of it commercially.
Bottom line
If you're running a small agency or managing multiple social accounts and don't want another SaaS subscription with a per-seat model, Postiz is the most complete self-hosted option I've found. The Temporal-backed scheduling is production-grade; the provider abstraction makes it realistic to add or maintain platform support; and the AI pipeline is useful rather than bolted on. The operational complexity is real, but it's the honest cost of self-hosting this category of tool.
