Skip to main content

Core concepts: posts and variants

Almost everything in WriteMars hangs off one idea: a post is the platform-agnostic container, and each platform it publishes to gets its own variant. Internalize this and the rest of the app reads naturally.

The mental model

Workspace (slug: my-team)
└── Post — "Launch announcement" ← title + working description, state, campaign, labels
├── Variant: linkedin ← the actual publishable content for LinkedIn
│ content, posting account, scheduled_at, status: draft → scheduled → published
├── Variant: twitter ← opening tweet + optional thread
└── Variant: another supported platform

A post is the platform-agnostic container (the idea, the draft, the workflow state). Each platform gets a variant holding the text that will actually be published, the connected account it posts from, and its own schedule. Rough ideas live on the post; publishable copy lives on variants.

Practical consequences:

  • Editing the post's description never changes what goes out — only variant content publishes.
  • Each variant schedules and publishes independently: LinkedIn on Tuesday, the X thread on Wednesday, same post.
  • A variant publishes from one connected posting account (social_profile_id in the API) — see Connect social accounts.
  • For X, the variant's content is the opening tweet; follow-up tweets form a thread.

One variant per platform per post

A post can have at most one variant per platform — it's enforced, not a convention. Asking for a second LinkedIn variant on the same post fails; update the existing one instead. Different messages for the same platform are different posts.

The variant lifecycle

Every variant carries a status:

StatusMeaning
draftDefault. Content in progress, nothing will happen on its own.
needs_approvalAwaiting approval — the variant targets a channel that needs sign-off. It moves on once an approver releases it.
scheduledHas a future publish time. WriteMars's scheduler publishes it automatically — no further action needed.
publishingIn flight right now. The variant is briefly locked against edits.
publishedLive. The publish time and the platform's post URL are recorded; the variant can no longer be scheduled or have its status changed.
failedThe publish attempt didn't succeed. The error message is stored on the variant; you can still edit and reschedule it after fixing the cause.

Moving through it:

  • draft → scheduled — set a future publish time (in the app: Schedule post on the post page; via API: set scheduled_at). Clearing the schedule (scheduled_at: null) returns it to draft.
  • scheduled → publishing → published — automatic at the scheduled time. You can also publish a variant immediately; same end states.
  • → failed — anything from an expired account connection to a platform rejection. Common causes in Publishing.

Scheduling to a channel you can't publish to directly never errors: the variant waits in needs_approval while the channel's approvers are asked to sign off, and approving releases it to scheduled. See approvals.

What surrounds a post

Posts live in a workspace — an isolated space with its own members, connected accounts, and content. Within it, each post sits in exactly one state (your pipeline: idea → drafting → review → …), can carry any number of labels for cross-cutting tags, and can belong to one campaign that groups related posts into a launch, event, or series.

Two more concepts decide who sees what. Every post has a visibility: private by default (only you and the people involved — channel owners, reviewers, approvers) or on the org calendar (the whole workspace). And every connected account is a channel with a sharing level set by its owner — private to them by default, optionally shared with admins or run as a company account. Both are covered in permissions.

One thing lives outside any post: calendar notes. These are lightweight, org-wide moments — a launch day, a holiday, a publishing freeze — that any member can drop onto the Calendar so the team plans around them.