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_idin 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:
| Status | Meaning |
|---|---|
draft | Default. Content in progress, nothing will happen on its own. |
needs_approval | Awaiting approval — the variant targets a channel that needs sign-off. It moves on once an approver releases it. |
scheduled | Has a future publish time. WriteMars's scheduler publishes it automatically — no further action needed. |
publishing | In flight right now. The variant is briefly locked against edits. |
published | Live. The publish time and the platform's post URL are recorded; the variant can no longer be scheduled or have its status changed. |
failed | The 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.