MIT licensed · 318 tests

One request in.
Ten platforms out.

Open-Dispatch is a self-hosted dispatch queue for social platforms. Your credentials never leave your machine, and nothing meters you per account.

dispatch.shPOST /dispatch
202 acceptedqueued as 7f3a91c4
10platform adapters
318tests, no network
3queue backends
5ways to install

What hosted schedulers take from you

Hosted tools
Open-Dispatch
Per-account fees that compound monthly
Self-host free, no ceilings
OAuth tokens held on someone else's servers
Credentials stay in your .env
Failures buried in a vendor dashboard
Every error visible in your queue, retryable
Inbox, analytics, and scheduling behind paid tiers
All ten platforms in one build
Your audience's data lingers after you cancel
Your queue, your disk, your call
Vendor lock-in by design
MIT licensed, fork it whenever

The whole path, end to end

A request enters the queue, a worker picks it up, an adapter publishes it, and a webhook reports the result. There is no hidden step between those four.

Adding a platform is one function

The worker owns retry, backoff, and webhook delivery, so an adapter only has to publish and report a result. Register it in one dict and document its format key. That is the whole integration surface.

adapters/myplatform.py
def publish(unit: ContentUnit, account: str | None) -> tuple[bool, str, str]:
    """Return (ok, post_id, error_message)."""
    # the worker handles retry, backoff, webhooks

What it does, with the artifact that proves it

One request, no dashboard

Every action is a plain HTTP call. Drive it from cron, n8n, a CI job, or an AI agent. The browser UI is optional, never required.

POST /dispatch

Three queue backends

JSONL on disk needs no infrastructure. Redis or Postgres when you outgrow one worker. Selected by an environment variable, not a rewrite.

JSONL | Redis | Postgres

Ten adapters, one contract

Every platform implements the same function signature. The worker owns retry, backoff, and webhooks so an adapter never has to.

tuple[bool, str, str]

Per-platform media specs

Ten image specs and seven video specs are built in. Send an image, get it back at the exact dimensions the target expects.

GET /media/specs

Every endpoint, no client library required

Plain HTTP with JSON in and JSON out. Full request and response shapes live on the API reference page.

API reference →
MethodPathPurpose
GET/healthzLiveness probe. JSON for clients, dashboard for browsers.
POST/dispatchEnqueue a ContentUnit for one or many platforms.
GET/queueList rows. Filter by queued, publishing, published, failed, dead, canceled.
GET/campaign/{unit_id}Every platform row for one dispatch.
POST/campaign/{unit_id}/cancelCancel a campaign's queued rows.
GET/queue/{id}One row, content-negotiated as JSON or HTML.
POST/queue/{id}/retryRe-queue an errored or dead row.
DELETE/queue/{id}Delete a queue row permanently.
POST/ai/adaptRewrite a caption per platform.
POST/media/transcodeResize an image to a platform spec.
GET/media/specsList the built-in image and video specs.

Five ways in, shortest first

macOS app

Open-Dispatch 0.4.0

macOS 13 Ventura or later · Apple Silicon and Intel

Download .dmg

all releases · build from source: bash scripts/make-dmg.sh

SwiftUI menubar app that bundles the Python server, so there is no separate Python install. First launch: right-click, then Open, to bypass Gatekeeper once.

Questions worth answering before you install

Which install method should I use?
On macOS, the app is the shortest path: one download, drag to Applications, done. If you want a terminal workflow, Homebrew gives you a login service via brew services. On a Linux VPS, Docker avoids the Python setup entirely.
How do I self-host on a VPS?
Docker is the cleanest option. It runs as a non-root user, needs no Python on the host, and ships an optional Redis sidecar. The Docker section of the README walks through it step by step.
Is it really free?
Yes. It is MIT licensed with no cloud component, no telemetry, and no account to create. Your platform credentials stay in a .env file on your own machine.
How do I reach the API?
It listens on localhost:8000 with no auth by default, which suits trusted self-hosting. If you expose the port beyond localhost, set OPEN_DISPATCH_API_TOKEN first. That requires an Authorization header on every route except /healthz.
Where do I get platform credentials?
Each platform's developer console issues its own keys, and .env.example in the repo lists every variable Open-Dispatch reads, grouped by platform. Copy it to .env and fill in only the platforms you dispatch to. The API skips platforms you leave unset.
Can I add another platform?
Yes. The adapter contract is a single Python function, roughly 80 lines. Register it in adapters/__init__.py, document its format key in the README, and the worker handles retry, backoff, and webhooks for you.

Run it yourself

318 tests · 10 adapters · 3 queue backends · MIT

Install GitHub