The open-source alternative to Typefully, Buffer, and Hypefury.
One lightweight binary. No dependencies. Full control.
Features
·
Platforms
·
Quick Start
·
Configuration
·
Tech Stack
- Single Binary Deployment - Everything compiled into one executable. No Docker required (but supported).
- Multi-Platform Posting - Schedule posts to X (Twitter), Mastodon, Bluesky, Threads, and LinkedIn.
- Media Uploads - Attach images and videos to posts. Platform-specific media requirements handled automatically.
- Post Threading - Create multi-post threads that publish sequentially across all platforms.
- Custom Mastodon Instances - Connect accounts from any number of Mastodon servers (configured via JSON env var).
- Workspaces & Teams - Multi-tenant architecture with role-based access control.
- Background Scheduling - SQLite-backed job queue survives server restarts.
- Encrypted Tokens - AES-256-GCM encryption for all OAuth tokens at rest.
- Modern UI - SvelteKit frontend with TailwindCSS, responsive design.
- Self-Hosted - Your data stays on your server. No third-party tracking.
| Platform | Status | Notes |
|---|---|---|
| X (Twitter) | ✅ Supported | OAuth 2.0 PKCE |
| Mastodon | ✅ Supported | Custom instances |
| Bluesky | ✅ Implemented | App passwords (no setup) |
| Threads | ✅ Implemented | Meta Graph API |
| ✅ Implemented | OAuth 2.0 + Posts API |
- Go 1.25+ (for building from source)
- Bun or npm (for frontend builds)
- OAuth credentials for the platforms you want to use
# Clone the repository
git clone https://github.com/rodrgds/openpost.git
cd openpost
# Install frontend dependencies and build
cd frontend
bun install
bun run build
# Build the Go binary (includes embedded frontend)
cd ../backend
cp .env.example .env
# Edit .env with your credentials
go build -o openpost ./cmd/openpost
# Run
./openpostThe application will start on http://localhost:8080.
# From project root
cd frontend && bun install && bun run build && cd ../backend && go build -o openpost ./cmd/openpostFor development, you can run the frontend and backend separately:
# Terminal 1: Frontend (with hot reload)
cd frontend
bun run dev
# Terminal 2: Backend (with hot reload)
cd backend
go run ./cmd/openpostThe frontend dev server runs on http://localhost:5173 and proxies API calls to the backend.
# Build the image
docker build -t openpost -f docker/Dockerfile .
# Run
docker run -d \
-p 8080:8080 \
-v openpost_data:/data \
-e JWT_SECRET=your-secret \
-e ENCRYPTION_KEY=your-encryption-key \
openpost# Build the image
docker build -t ghcr.io/rodrgds/openpost:latest -f docker/Dockerfile .
# Tag for version
docker tag ghcr.io/rodrgds/openpost:latest ghcr.io/rodrgds/openpost:v0.1.0
# Push to registry
docker push ghcr.io/rodrgds/openpost:latest
docker push ghcr.io/rodrgds/openpost:v0.1.0# docker-compose.yml
services:
openpost:
image: ghcr.io/rodrgds/openpost:latest
restart: unless-stopped
environment:
- JWT_SECRET=your-secret
- ENCRYPTION_KEY=your-encryption-key
volumes:
- openpost_data:/data
ports:
- "8080:8080"All configuration is done via environment variables or a .env file:
| Variable | Required | Description |
|---|---|---|
JWT_SECRET |
✅ Yes | Secret key for JWT tokens (32+ chars) |
ENCRYPTION_KEY |
✅ Yes | AES-256 key for token encryption |
OPENPOST_MEDIA_PATH |
No | Local media storage path (default: ./media) |
OPENPOST_MEDIA_URL |
No | URL path for serving media (default: /media) |
TWITTER_CLIENT_ID |
For X | Twitter OAuth client ID |
TWITTER_CLIENT_SECRET |
For X | Twitter OAuth secret |
MASTODON_SERVERS |
For Mastodon | JSON array of Mastodon server configs |
MASTODON_REDIRECT_URI |
No | OAuth callback URI (default: OOB) |
LINKEDIN_CLIENT_ID |
For LinkedIn | LinkedIn OAuth client ID |
LINKEDIN_CLIENT_SECRET |
For LinkedIn | LinkedIn OAuth secret |
OPENPOST_DISABLE_LINKEDIN_THREAD_REPLIES |
No | Disable LinkedIn thread child replies when app lacks w_member_social_feed |
THREADS_CLIENT_ID |
For Threads | Meta App ID |
THREADS_CLIENT_SECRET |
For Threads | Meta App Secret |
OPENPOST_PORT |
No | Server port (default: 8080) |
OPENPOST_DB_PATH |
No | SQLite database path |
OPENPOST_FRONTEND_URL |
No | CORS origin for frontend |
Note: Bluesky doesn't require any env vars - users connect directly with their handle and app password.
# Generate JWT secret
openssl rand -base64 32
# Generate encryption key
openssl rand -base64 32- Go to Twitter Developer Portal
- Create a new App
- Enable OAuth 2.0
- Set callback URL:
http://localhost:8080/api/v1/accounts/x/callback - Copy Client ID and Secret to your
.env
Mastodon supports multiple servers via the MASTODON_SERVERS environment variable. Each server needs its own OAuth app because Mastodon client credentials are per-instance.
- For each Mastodon instance, go to Settings → Development → New Application
- Set the redirect URI to:
urn:ietf:wg:oauth:2.0:oob(or your callback URL) - Copy the Client ID and Secret
- Add to your
.env:
MASTODON_REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob
MASTODON_SERVERS='[
{"name":"Personal","client_id":"abc123","client_secret":"xyz789","instance_url":"https://mastodon.social"},
{"name":"Work","client_id":"def456","client_secret":"uvw012","instance_url":"https://fosstodon.org"}
]'The name is a label shown in the UI. You can configure as many servers as you need.
Bluesky uses app passwords - no setup needed. Users just enter their handle and app password when connecting:
- Go to Bluesky App Passwords
- Create a new app password
- In OpenPost, click Connect on Bluesky and enter your handle + app password
See docs/bluesky-integration.md for more details.
- Go to LinkedIn Developer Portal
- Create a new app and request "Share on LinkedIn" product
- Request approval for
w_member_social_feed(Social Actions create) in your app products/permissions - Add redirect URL:
http://localhost:8080/api/v1/accounts/linkedin/callback - Copy Client ID and Secret to your
.env
Important: LinkedIn thread replies (posting comments on the first post) require w_member_social_feed approval. If your app only has w_member_social, first posts may succeed but replies/comments will fail with ACCESS_DENIED: partnerApiSocialActions.CREATE.
See docs/linkedin-integration.md for detailed setup instructions.
- Go to Meta for Developers
- Create a new app (Business type) and add Threads API product
- Add redirect URL:
http://localhost:8080/api/v1/accounts/threads/callback - Copy App ID and App Secret to your
.envasTHREADS_CLIENT_IDandTHREADS_CLIENT_SECRET
See docs/threads-integration.md for detailed setup instructions.
Frontend:
- SvelteKit 5 (with runes)
- TailwindCSS 4
- Paraglide (i18n)
- Vitest (testing)
Backend:
- Go 1.25+ (Echo framework)
- SQLite (Bun ORM)
- Background job queue (SQLite-backed polling)
Deployment:
- Single Go binary with embedded static files
- Docker support
- Zero external dependencies (no Redis, no PostgreSQL required)
openpost/
├── frontend/ # SvelteKit frontend (web + Android app)
│ ├── src/
│ │ ├── lib/
│ │ │ ├── api/ # API client (openapi-fetch)
│ │ │ ├── components/# UI components
│ │ │ └── stores/ # Auth, UI state
│ │ └── routes/ # SvelteKit routes
│ ├── android/ # Android native app (Capacitor)
│ └── package.json
│
├── backend/
│ ├── cmd/openpost/ # Main entry point
│ │ └── public/ # Embedded SvelteKit build output (not source)
│ ├── internal/
│ │ ├── api/ # HTTP handlers & middleware
│ │ │ ├── handlers/ # Posts, Auth, Media, OAuth handlers
│ │ │ └── middleware/
│ │ │ └── auth.go# JWT authentication middleware
│ │ ├── config/ # Configuration loading
│ │ ├── database/ # SQLite setup
│ │ ├── models/ # Bun ORM models
│ │ │ ├── models.go
│ │ │ └── models_test.go
│ │ ├── platform/ # Platform adapter interface + implementations
│ │ │ ├── adapter.go # PlatformAdapter interface
│ │ │ ├── http.go # Shared HTTP helpers
│ │ │ ├── x.go # Twitter/X adapter
│ │ │ ├── mastodon.go# Mastodon adapter
│ │ │ ├── bluesky.go # Bluesky adapter
│ │ │ ├── linkedin.go# LinkedIn adapter
│ │ │ └── threads.go # Threads adapter
│ │ ├── queue/ # Background job worker
│ │ └── services/ # Business logic
│ │ ├── auth/ # JWT & password handling
│ │ │ ├── auth.go
│ │ │ └── auth_test.go
│ │ ├── crypto/ # Token encryption
│ │ │ ├── encrypt.go
│ │ │ └── encrypt_test.go
│ │ ├── mediastore/ # Local/S3 media storage
│ │ ├── publisher/ # Post publishing logic
│ │ └── tokenmanager/ # Token refresh management
│ ├── .golangci.yml # Linter configuration
│ ├── go.mod # Go module definition
│ └── go.sum # Go module checksums
│
├── docs/ # Platform integration docs
├── AGENTS.md # AI agent guidelines
├── PLAN.md # Implementation roadmap
└── README.md
- Tokens are encrypted at rest using AES-256-GCM
- Passwords hashed with bcrypt
- JWT authentication with configurable expiry
- No external services - all data stays on your server
- OAuth PKCE for Twitter authentication
See PLAN.md for the complete implementation status and roadmap.
- User authentication (register/login)
- Workspace management (multi-tenant)
- Twitter/X OAuth
- Mastodon OAuth (multi-server support)
- Bluesky OAuth (AT Protocol)
- LinkedIn OAuth
- Threads OAuth (Meta Graph API)
- Post scheduling with background worker
- Media upload support
- Post threading (multi-post threads)
- Single binary deployment
- Token refresh for all platforms
- Platform adapter architecture
- Post analytics
- Email notifications
- Webhook support
We welcome contributions! Please see AGENTS.md for development guidelines.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by the OpenPost community