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.jsonrewrites/_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 theAuthorizationheader. 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_NAMEis set; presign endpoints return503when 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, sharedCOLLAB_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
AuthSessionDB table; theTokenAuthenticationmiddleware validatesAuthorization: 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 assocle_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
SocialProfilerecords 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.