Articles

Create an article

Writes a new article and returns it. Needs content: write, and is a write.

The article's text goes in content_markdown, as Markdown: the same rendition GET /articles/{article} returns as content_markdown under ?expand=content, so what you read back is what you send. HTML is not accepted. Commune parses it strictly: text it cannot read answers 400 naming the line, and nothing is written, rather than guessing at a document you did not write and sending it to your list.

What you get is always a draft. status, posted_at and scheduled_for are not properties of the request. A draft is invisible on every reader surface, so nothing this operation does reaches anybody. It goes out through Schedule an article (POST /articles/{article}/schedule) or Send an article to the list (POST /articles/{article}/send), each of which runs the six gates described on the second before its email leaves Commune.

Send no body at all and Commune seeds one. {} creates an empty untitled draft: two blank lines and an editable unsubscribe line. Send a body and it is stored exactly as sent, with nothing appended. The send operations refuse an article whose body carries no unsubscribe mechanism and no postal address, so a body you intend to send should carry {{ unsubscribe_url }} and {{ address }}.

Only a newsletter Commune publishes. A newsletter whose esp is anything but commune has its articles written elsewhere and mirrored into Commune afterwards, so there is nothing here to create. That answers 422 with the code not_commune_newsletter, whose docs_url is https://usecommune.dev/errors/not_commune_newsletter, the page on what the refusal means and how to move a newsletter onto Commune.

Publishes no event. A draft has neither gone out nor been queued, and status is what says so until one of those happens.

The Markdown content_markdown takes

Headings, paragraphs, bold, italic, strikethrough, inline code, links, images, blockquotes, bullet and ordered lists, fenced code blocks with a language, tables and thematic breaks. A line ending in two spaces or a backslash is a line break; a code fence without a language is stored without one rather than guessed at.

Merge tags survive exactly as written. {{ subscriber.first_name }} and {% if %} are personalization rather than Markdown, so nothing inside a Liquid construct is escaped or read as formatting.

Raw HTML is refused rather than passed through or dropped. Write a literal < as \<.

The five components

Five things the editor can hold have no Markdown spelling, so they get an MDX-shaped syntax. The set is closed: any other tag name is a 400.

  • <Section> ... </Section> wraps blocks in a styled band. Optional backgroundColor, textColor (hex, with the #), fontFamily (sans, serif, mono), fontSize (a number, in px) and textAlign (left, center, right).
  • <EmailOnly> ... </EmailOnly> wraps blocks that belong in the inbox and not on the web. This is where the unsubscribe line and the mailing address go: on the website there is no subscriber, so the link is dead and the address is noise. Commune seeds exactly this into a draft created with no body.
  • <Button href="...">Label</Button> is a call to action. href is required; alignment is optional.
  • <Socials items={[...]} /> is a row of linked platform icons. items is required and is a JSON array of {"platform": "...", "url": "...", "imageUrl": null}. Optional align, iconColor and iconBgColor.
  • <YouTube url="..." /> is a video. The URL has to be one Commune can read a video id out of (youtube.com/watch?v=, youtu.be/, youtube.com/shorts/ or youtube.com/embed/), because the email shows a thumbnail built from that id rather than an iframe, which every major email client strips.

Attributes are written name="value" or name={json}. A component that holds nothing is written self-closing; one that holds content is opened and closed on their own lines.

post/newsletters/{newsletter}/articles

Request

  • Base URL: https://api.usecommune.com
  • URL: https://api.usecommune.com/newsletters/{newsletter}/articles
  • Auth: one of:
    • HTTP bearer
    • OAuth 2 (authorization code, authorization URL https://usecommune.com/api/oauth/authorize, token URL https://usecommune.com/api/oauth/token, scopes: content:write)

Headers

Idempotency-Keystring required

A value of your choosing naming the change this request is making.

Send the same value again to retry the same request. Commune replays the answer the first attempt gave instead of making the change twice, and marks the replay with an Idempotent-Replay: true response header. Send a different value for a different change: a key reused for a request that differs in any way answers 409, because replaying an answer to a question you did not ask is a wrong answer you could not detect.

A UUID per change is the usual choice. Remembered for 24 hours, per credential, so two credentials choosing the same value never see each other's answers.

Required, not optional.

Request body

titlestring nullable

The subject line. Null or absent leaves the article untitled, which is legal: a draft is often started before it is named.

preview_textstring nullable

The short line email clients show after the subject.

image_urlstring uri nullable

The cover image. Leave it out and Commune stamps the first image in the body when the article is sent.

slugstring

The article's URL segment, unique within the newsletter. Lowercase letters, digits and single hyphens.

Leave it out and Commune derives one from the title, adding a numeric suffix if that one is taken. An untitled article falls back to its short_id. A slug you choose yourself is never renamed for you: one that is already taken answers 422 rather than quietly becoming something else, because a permalink you did not choose is worse than an error you can act on.

content_markdownstring

An article's text, as Markdown. Create an article documents the Markdown Commune accepts.

Example request

{
  "title": "What we learned in March",
  "preview_text": "The third one surprised us.",
  "content_markdown": "# What we learned in March\n\nThree things, and the **third** one surprised us.\n\n<EmailOnly>\n[Unsubscribe]({{ unsubscribe_url }}) | {{ address }}\n</EmailOnly>\n"
}

Response

The article, as created: a draft, with the id, short_id and slug every other operation addresses it by. Read it back with GET /articles/{article} to see the stored body.

object'article' required

Always article.

idstring uuid required

Stable identifier.

short_idstring required

Eight character base62 identifier, unique across Commune. Safe in a URL and accepted anywhere {article} is.

slugstring required

URL segment under the newsletter, unique within it but not across Commune. The permalink is /n/{handle}/a/{slug}. Falls back to the short_id for an untitled article.

titlestring nullable

Subject line of the article. Null for an untitled draft.

preview_textstring nullable

The short line email clients show after the subject, and what Commune uses as the excerpt on a card.

image_urlstring uri nullable

Cover image. When the creator set none, Commune stamps the first image in the body at send time, so this is usually populated for a sent article.

external_urlstring uri nullable

The article's canonical URL on the newsletter's own provider, for an imported article. Null for one written in Commune.

status'draft' | 'scheduled' | 'sending' | 'sent' | 'failed' | 'archived' required

Where an article is in its life. A credential holding only read permissions ever sees sent and nothing else. An imported article is always sent, since Commune sees it after the provider delivered it.

is_importedboolean required

true when the article came in from the newsletter's provider, false when it was written and sent in Commune.

posted_atstring date-time nullable

When the article went out. An article dated in the future is not returned by any read operation until that moment passes, so this is never ahead of now in a response.

scheduled_forstring date-time nullable

When a queued article may go out. Set while status is scheduled and null otherwise.

This is not posted_at and the difference matters: a queued article has no publication date yet, which is why it stays invisible on every reader surface until it really goes out. Commune dispatches in passes, so this is the moment from which the article may go rather than the moment it will.

created_atstring date-time required

When the row was created in Commune.

updated_atstring date-time

When the article was last edited.

Example response

{
  "short_id": "k7Rm2xQp",
  "newsletter": {
    "object": "newsletter",
    "id": "9a4c1c6e-0f2b-4f47-9d3f-6d1b1a2c3d4e"
  },
  "authors": [
    {
      "object": "newsletter",
      "id": "9a4c1c6e-0f2b-4f47-9d3f-6d1b1a2c3d4e"
    }
  ],
  "thread": {
    "object": "newsletter",
    "id": "9a4c1c6e-0f2b-4f47-9d3f-6d1b1a2c3d4e"
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.