1
0
Fork
You've already forked TootPulse
0
Social Media Manager für Mastodon und LinkedIn - mit Genehmigungsverfahren / -workflow https://tootpulse.server.joram.eu/
  • TypeScript 97.8%
  • PLpgSQL 1.3%
  • CSS 0.9%
2026年06月03日 19:00:00 +02:00
.vscode Latest snapshot 2026年06月03日 19:00:00 +02:00
app Latest snapshot 2026年06月03日 19:00:00 +02:00
components Latest snapshot 2026年06月03日 19:00:00 +02:00
hooks Latest snapshot 2026年06月03日 19:00:00 +02:00
lib Latest snapshot 2026年06月03日 19:00:00 +02:00
public Latest snapshot 2026年06月03日 19:00:00 +02:00
scripts Latest snapshot 2026年06月03日 19:00:00 +02:00
styles Latest snapshot 2026年06月03日 19:00:00 +02:00
supabase/migrations Latest snapshot 2026年06月03日 19:00:00 +02:00
.gitignore Latest snapshot 2026年06月03日 19:00:00 +02:00
components.json Latest snapshot 2026年06月03日 19:00:00 +02:00
LICENSE Latest snapshot 2026年06月03日 19:00:00 +02:00
middleware.ts Latest snapshot 2026年06月03日 19:00:00 +02:00
next-env.d.ts Latest snapshot 2026年06月03日 19:00:00 +02:00
next.config.mjs Latest snapshot 2026年06月03日 19:00:00 +02:00
package-lock.json Latest snapshot 2026年06月03日 19:00:00 +02:00
package.json Latest snapshot 2026年06月03日 19:00:00 +02:00
pnpm-lock.yaml Latest snapshot 2026年06月03日 19:00:00 +02:00
postcss.config.mjs Latest snapshot 2026年06月03日 19:00:00 +02:00
README.md Latest snapshot 2026年06月03日 19:00:00 +02:00
tsconfig.json Latest snapshot 2026年06月03日 19:00:00 +02:00
vercel.json Latest snapshot 2026年06月03日 19:00:00 +02:00

TootPulse

Open-source social media management for your team. Schedule, approve, and publish posts to Mastodon, LinkedIn, and more from a single dashboard.

Features

  • Multi-platform publishing -- Connect multiple accounts per project across Mastodon, LinkedIn, Bluesky, Threads, Reddit, Facebook Pages, Instagram, and more — including email newsletters via Buttondown, Resend, Mailgun, Brevo, Mailchimp, SendGrid, ConvertKit, and Beehiiv. Choose which connections each post publishes to.
  • Team collaboration -- Invite members by email. Assign admin or member roles. Pending invites auto-accept on signup.
  • Approval workflows -- Optional sequential or parallel approval groups. Admins can reorder groups and toggle the entire workflow on or off per project.
  • Scheduling -- Schedule posts for future dates. A cron job (/api/cron/publish) automatically publishes approved posts when their scheduled time arrives.
  • Calendar and timeline views -- Visualise scheduled posts in a monthly calendar grid or a chronological timeline list.
  • Edit history -- Every content change is tracked with diffs and author attribution.
  • Invite-only mode -- Public sign-up can be disabled via an environment variable, restricting access to email-invited users only.

Tech stack

Layer Technology
Framework Next.js 16 (App Router)
Language TypeScript
Database & Auth Supabase (PostgreSQL + Row Level Security + Auth)
UI shadcn/ui, Tailwind CSS v4, Radix primitives
Deployment Vercel

Getting started

Prerequisites

  • Node.js 18+
  • A Supabase project
  • A Vercel account (optional, for deployment)

1. Clone and install

git clone https://github.com/joramulmke/TootPulse.git
cd TootPulse
npm install

2. Environment variables

Create a .env.local file (or set these in your Vercel project settings):

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# NEXT_PUBLIC_APP_URL=your-url
# instead 
# Go to your Supabase project dashboard: **Authentication > URL Configuration**
# Set **Site URL** to your deployed app URL (e.g. `https://tootpulse.app`)
# Under **Redirect URLs**, add your deployed URL with a wildcard pattern: `https://yourdomain.com/**`
# Sign-up control
# Set to "false" to disable public registration (invite-only mode)
NEXT_PUBLIC_SIGNUP_ENABLED=false
# App name (optional) — customize the displayed app name in the UI
# Defaults to "TootPulse" if not set
NEXT_PUBLIC_APP_NAME=TootPulse
# App tagline (optional) — customize the tagline shown in the footer
# Defaults to "Open-source social media management for your team." if not set
NEXT_PUBLIC_APP_TAGLINE=Open-source social media management for your team.
# Legal
NEXT_PUBLIC_IMPRESSUM_URL=your-legal-url
# Donation link (optional) — shown in the user dropdown menu above Sign out
NEXT_PUBLIC_DONATION_URL=your-donation-url
# Cron secret (protects the /api/cron/publish endpoint)
CRON_SECRET=your-cron-secret
# * * * * * curl -s -H "Authorization: Bearer YOUR_CRON_SECRET" https://your-app.com/api/cron/publish
# OAuth (optional — enables one-click OAuth for LinkedIn, Threads, and Reddit)
# Without these, users enter credentials manually. Both modes coexist.
# See "OAuth setup" below for how to obtain these values.
LINKEDIN_CLIENT_ID=your-linkedin-client-id
LINKEDIN_CLIENT_SECRET=your-linkedin-client-secret
THREADS_CLIENT_ID=your-threads-client-id
THREADS_CLIENT_SECRET=your-threads-client-secret
REDDIT_CLIENT_ID=your-reddit-client-id
REDDIT_CLIENT_SECRET=your-reddit-client-secret
# Shared for Facebook Page and Instagram OAuth
FACEBOOK_APP_ID=your-facebook-app-id
FACEBOOK_APP_SECRET=your-facebook-app-secret

3. Database setup

The database schema is managed through Supabase migrations. The key tables are:

  • profiles -- User display names and avatars (synced from auth.users)
  • projects -- Workspace containers with approval and schedule settings
  • project_members -- User-to-project membership with roles
  • project_invites -- Email-based invitations with auto-accept on signup
  • social_connections -- Platform credentials (Mastodon, LinkedIn) per project
  • posts -- Content with status, scheduling, and language metadata
  • post_connections -- Many-to-many link between posts and social connections, tracking per-connection publish status
  • approval_groups -- Ordered approval stages per project
  • post_approvals -- Per-group approval decisions on each post
  • post_edits -- Full edit history with diffs

Row Level Security (RLS) is enabled on all tables, scoped through is_project_member() and is_project_admin() helper functions.

4. Run locally

npm run dev

Open http://localhost:3000.

Project structure

app/
 page.tsx # Landing page
 auth/ # Login, sign-up, sign-up success
 dashboard/
 page.tsx # Project list
 calendar/page.tsx # Cross-project calendar
 project/[projectId]/
 page.tsx # Project detail (posts, schedule, settings)
 posts/new/page.tsx # Create post
 posts/[postId]/page.tsx # Post detail with approvals
 posts/[postId]/edit/page.tsx # Edit post
 api/cron/publish/route.ts # Scheduled publishing cron
 api/oauth/
 linkedin/route.ts # LinkedIn OAuth initiation
 linkedin/callback/route.ts # LinkedIn OAuth callback
 threads/route.ts # Threads OAuth initiation
 threads/callback/route.ts # Threads OAuth callback
 reddit/route.ts # Reddit OAuth initiation
 reddit/callback/route.ts # Reddit OAuth callback
 facebook/route.ts # Facebook Page OAuth initiation
 facebook/callback/route.ts # Facebook Page OAuth callback
 instagram/route.ts # Instagram OAuth initiation
 instagram/callback/route.ts # Instagram OAuth callback
components/
 dashboard/ # Dashboard shell, project list
 project/ # Post form, post detail, settings,
 # connection manager, member manager,
 # approval groups, calendar, timeline
 footer.tsx # Global footer
 ui/ # shadcn/ui primitives
lib/
 types.ts # Shared TypeScript types
 oauth-state.ts # Nonce-based CSRF state for OAuth flows
 char-limits.ts # Per-platform character limits
 csv-import.ts # Bulk post import from CSV
 approval.ts # Approval workflow helpers
 utils.ts # General utilities
 {platform}.ts # One file per platform (mastodon, linkedin,
 # bluesky, pixelfed, threads, tumblr, reddit,
 # telegram, discord, webhook, pinterest,
 # facebook, instagram, wordpress, httprequest,
 # ntfy, matrix, unifiedpush, slack, teams,
 # buttondown, resend, mailgun, brevo,
 # mailchimp, sendgrid, convertkit, beehiiv)
 actions/
 connection-actions.ts # CRUD for social connections
 post-actions.ts # CRUD for posts
 project-actions.ts # Projects, members, invites, approval groups
 publish-actions.ts # Multi-platform publishing logic

OAuth setup

If you configure OAuth credentials for LinkedIn, Threads, Reddit, Facebook, or Instagram, users see a "Connect with [Platform]" button instead of manually entering tokens. Both flows always coexist — a "enter manually" toggle is available as a fallback.

LinkedIn

  1. Go to developers.linkedin.com and create an app.
  2. Under Auth, add {APP_URL}/api/oauth/linkedin/callback as an authorized redirect URL.
  3. Under Products, request access to Sign In with LinkedIn using OpenID Connect (grants openid profile) and Share on LinkedIn (grants w_member_social).
  4. For company page posting, also enable the Advertising API product (grants w_organization_social).
  5. Copy the Client ID and Client Secret into your env vars.

Threads

  1. Go to developers.facebook.com and create a Meta app with the Threads API product.
  2. Under Threads API > Settings, add {APP_URL}/api/oauth/threads/callback as a valid OAuth redirect URI.
  3. Copy the App ID (as THREADS_CLIENT_ID) and App Secret (as THREADS_CLIENT_SECRET) into your env vars.

Reddit

  1. Go to reddit.com/prefs/apps and create a web app (not script).
  2. Set the redirect URI to {APP_URL}/api/oauth/reddit/callback.
  3. Copy the client ID (shown below the app name) and secret into your env vars.

Reddit OAuth tokens expire after 1 hour. TootPulse automatically refreshes them using the stored refresh token when a publish fails with a 401 — no action needed.

Facebook Page

  1. Go to developers.facebook.com and create a Meta app.
  2. Add the Facebook Login product and under its settings add {APP_URL}/api/oauth/facebook/callback as a valid OAuth redirect URI.
  3. Request the permissions pages_show_list, pages_read_engagement, and pages_manage_posts.
  4. Copy the App ID (as FACEBOOK_APP_ID) and App Secret (as FACEBOOK_APP_SECRET) into your env vars.
  5. When connecting, users enter their Page ID first (found in Facebook Page Settings → About), then authorize via OAuth. The Page access token is fetched automatically.

Facebook Page access tokens obtained via a long-lived user token do not expire as long as the user keeps the app authorized. If publishing fails with a token error, reconnect the page via OAuth.

Instagram

  1. Use the same Meta app as Facebook (same FACEBOOK_APP_ID / FACEBOOK_APP_SECRET).
  2. Add the Instagram Graph API product to your Meta app.
  3. Under Instagram Graph API > Settings, add {APP_URL}/api/oauth/instagram/callback as a valid OAuth redirect URI.
  4. Request the permissions instagram_basic, instagram_content_publish, and pages_show_list.
  5. The connected Instagram account must be a Business or Creator account linked to a Facebook Page.

Instagram requires an image on every post — text-only posts will not publish to Instagram. Tokens expire after ~60 days; reconnect if publishing fails.

Email newsletters

No OAuth is required for any email platform. Credentials are entered manually in the connection form (API key plus sender details). The post title is used as the email subject line (falls back to the first line of content if no title is set).

Platform-specific notes:

  • Mailchimp — the API key must include the server suffix (e.g. abc123-us1)
  • SendGrid — requires a sender_id (the numeric ID of a verified sender in your SendGrid account)
  • Resend / ConvertKit — two-step flow: create draft, then publish/send
  • Brevo — two-step flow: create campaign, then send
  • Mailchimp — three-step flow: create campaign → set content → send
  • Buttondown / Beehiiv — single-step send

Adding a social connection

  1. Open a project and go to Settings > Connections.
  2. Click Add Connection and choose a platform.
  3. For Mastodon: enter the instance URL (e.g. https://mastodon.social) and an access token generated from your Mastodon account settings.
  4. For LinkedIn / Threads / Reddit / Facebook / Instagram: click Connect with [Platform] if OAuth is configured, or use the manual token form.
  5. Click Test to verify the credentials, then Save.

Scheduled publishing

Posts with a scheduled_at date are automatically published by the cron endpoint at /api/cron/publish. Configure a Vercel Cron Job or an external scheduler to call this endpoint periodically:

// vercel.json
{
 "crons": [
 {
 "path": "/api/cron/publish",
 "schedule": "*/5 * * * *"
 }
 ]
}

The endpoint is protected by the CRON_SECRET environment variable.

License

This project is open source. See the repository for license details.