Skip to main content

System architecture

A developer-facing map of the moving parts. For code conventions see backend/CLAUDE.md and frontend/CLAUDE.md in the repository.

Components

WriteMars is a single-page app talking to a Django API, with Celery doing all time-based work.

  • Frontend — React 18 + Vite SPA on Vercel. frontend/vercel.json rewrites /_backend/* to the Django API so the API is same-origin with the app. That keeps auth first-party: the SPA reads the access-token cookie and sets the Authorization header. In development the Vite dev-server proxy does the same job.
  • API — Django 5.2 + DRF on Railway. Serves the REST API under /api/, auth under /auth/, the Django admin, and the MCP server at /mcp/. Runs as gunicorn behind Railway's router.
  • PostgreSQL — the system of record (Railway managed Postgres). No SQLite in any environment.
  • Redis — Django cache, auth throttling store, and the Celery broker + result backend.
  • Celery worker — publishes scheduled variants, runs auto-engagement (comment / like / repost), and syncs analytics + follower counts. Heavy jobs live under writemars/services/bg_tasks/ so they can also run from management commands.
  • Celery beat — the scheduler. Schedule (in writemars/settings/common.py): publish due variants every 30 s, send upcoming-publish reminders every 5 min, execute engagements every 60 s, sync post analytics every 15 min, sync follower counts every 60 min, and refresh stale voice profiles daily. Run exactly one beat replica — duplicates would double-publish.
  • Object storage — S3 or any S3-compatible API (MinIO) via boto3 for post media. Optional until AWS_S3_BUCKET_NAME is set; presign endpoints return 503 when unconfigured.
  • Email — transactional email through Resend.
  • AI router — Baseten as the primary model provider with Anthropic as fallback, powering AI drafting, repurposing, reusable skill generation, and voice features.
  • Realtime collaboration server — a standalone Node service (collab/, Hocuspocus v3 + Yjs, Node ≥ 22) deployed on Railway that backs live co-editing in the post editor only. The SPA connects over WebSocket (VITE_COLLAB_URL); the server authorizes each document and seeds/persists variant content through Django internal endpoints (DJANGO_INTERNAL_URL, shared COLLAB_SERVICE_SECRET).
  • MCP server — a first-party Model Context Protocol server at /mcp/ (Streamable HTTP, PAT auth) that lets agents like Claude Code drive a workspace. See MCP overview.

Auth model

Two credential styles, one token store:

  • Browser sessions — sign-in issues a short-lived access token (in the response body, mirrored to a cookie for the SPA) plus a longer-lived HttpOnly refresh-token cookie. Tokens live in an AuthSession DB table; the TokenAuthentication middleware validates Authorization: Bearer <token>.
  • Personal access tokens (PATs) — the credential for the REST API and MCP server, created under Profile → API Tokens. New tokens use the writemars_pat_ prefix; tokens issued earlier as socle_pat_ remain valid. A PAT acts as you, carries your roles, and can be scoped to one workspace.
  • OAuth sign-in — Google and GitHub.
  • Social connects — LinkedIn and Twitter/X. These are separate from sign-in: they authorize WriteMars to publish and run engagement on your behalf, stored as SocialProfile records rather than auth accounts.

Permission model

Access is scoped by workspace role (owner / admin / member / viewer) for workspace-level actions, but publishing is governed by the channel (a connected social account) as the permission boundary: approval is inherent, not a toggle — only a channel's owner, its trusted members, and (on company channels only) admins publish to it directly; everyone else's variants route to that channel's approvers before they can go live. Admins have no implicit power over personal channels. Post visibility (private vs on the org calendar) further controls who can see a post. See permissions for the product-facing version.