All work

Case study 01 / 06

Crosspine

Publish Once, Everywhere

A multi-tenant social publishing workspace. One draft, fitted to each network's rules, approved by your team, and published on schedule to twelve platforms from a single calendar.

Project
Crosspine
Year
2026
Role
Design & development
Stack
Next.js 16, TypeScript, Prisma, PostgreSQL, BullMQ, Stripe
Crosspine – Publish Once, Everywhere screenshot
Fig. 01Crosspine, 2026

01/Problem

The problem

Publishing the same idea to a dozen social networks means a dozen logins, a dozen sets of rules, and a dozen chances to get it wrong. Every platform has its own character limit, media count, aspect ratio, and video length, and most of them only tell you the rule was broken after you have already hit publish.

Doing this as a team adds a second problem: someone has to review a post before it goes out, and a scheduling queue on its own has no idea who is allowed to approve what.

  • A dozen tabs: posting the same thing everywhere means logging into every network in turn and pasting the same text by hand.

  • Rules you find out about too late: character limits, media counts, aspect ratios, and video lengths differ per network, and most only complain after you publish.

  • No room for a team: a scheduling queue on its own has no notion of who drafts, who approves, and what is allowed to go out unreviewed.

  • Publishing quietly fails: expired tokens, rate limits, and platform outages turn a scheduled post into silence unless something is watching and retrying.

Crosspine: research and exploration
Fig. 02Crosspine: research and exploration

02/Approach

Goals and approach

Crosspine is a composer, a calendar, and a queue. A post is written once, then overridden per channel where a network needs different wording, and validated against that network's real constraints before it can be scheduled at all. The calendar shows everything queued across every connected account, with drag to reschedule.

Posts created by members enter an approval workflow, while admins and owners publish directly. Media is uploaded straight to object storage, probed for its real type, and processed into thumbnails and renditions with alt text.

Once a post is scheduled, a background worker owns it: it publishes through each network's official API, retries what is retryable, and reports the platform's actual error when something is not. Every network is tracked on its own, so one failed publish never holds back the others.

  • Put every network behind one adapter interface, so adding a platform never means touching the scheduler.

  • Validate a post against each network's real constraints before it can be scheduled, not after it fails.

  • Make workspaces, roles, and approval first-class rather than bolted onto a single-user tool.

  • Encrypt channel tokens at rest and scope every query to one workspace at a single enforced choke point.

Crosspine: the solution in use
Fig. 03Crosspine: the solution in use

03/Architecture

How it is built

Two processes share one TypeScript codebase: a Next.js 16 App Router web app and a long-running BullMQ worker, both importing the same domain code. PostgreSQL through Prisma with the pg driver adapter, Redis for queues and fixed-window rate limits, and S3-compatible object storage for media.

Every network sits behind one adapter interface covering validation, publishing, credential refresh, metrics, and the OAuth handshake, so adding a platform means writing one adapter rather than touching the scheduler. Adapter errors are normalised into token, retryable, and permanent so the worker knows whether to retry, reconnect, or stop. Jobs are identified by post, channel, and scheduled time, which makes the queue idempotent across crashes and reschedules.

Channel tokens are AES-256-GCM encrypted at rest behind a versioned key, and every query is scoped to one workspace at a single service-layer choke point, with cross-workspace access returning a 404 rather than a permission error. Better Auth handles sessions, social login, TOTP two-factor, and organisations.

The whole stack ships as Docker containers behind Traefik, applying database migrations on boot and refusing to start if a production secret is still a placeholder.

  • A composer with per-channel overrides and live constraint validation, backed by twelve platform adapters.

  • A calendar with drag to reschedule, and a media pipeline that probes uploads and builds thumbnails, renditions, and alt text.

  • A background worker with idempotent job ids, automatic retries for what is retryable, and normalised platform errors for what is not.

  • An approval workflow for team members, analytics gathered on a schedule, and an AI assistant for drafting and rewriting captions.

Crosspine: system architecture
Fig. 04Crosspine: system architecture

04/Results

What happened

Crosspine is live at crosspine.io. A post goes out once and lands on every network it was written for, shaped correctly for each. A team member drafts, an admin approves, and the queue publishes on schedule without anyone watching it. Failures retry on their own and surface the platform's real error instead of a generic one. Connected accounts, media, scheduling, and analytics live in one workspace, and supporting another network means writing one adapter against an interface that already exists.

Visit crosspine.io

Ninibot

Ask me anything about Nicolas Laforet and his work.