Skip to the content.

Publish a weekly changelog post with an image, a poll, and an email to every member

The community update that most founders publish by hand every week: an image, a poll to drive replies, and the email blast so people who don’t open the app still find out. All three are the pieces Skool’s API exposes least obviously — and each one fails in a way that looks like success.

Production version: the Monday “Novedades de la comunidad” post in a 1,700-member community, previously the one task that could not be automated.

What you’ll build

[Your changelog source: repo, notes, CMS]
        ↓
   1. files:uploadImage      → fileId
   2. posts:createPoll       → pollId
   3. posts:create           → post + poll + image + email to all members
        ↓
   4. Verify by re-fetching (never by status code)

Prerequisites

Step 1 — Upload the image

{
  "action": "files:uploadImage",
  "cookies": "...",
  "groupSlug": "your-community",
  "params": {
    "filePath": "/local/path/changelog.png",
    "fileName": "changelog.png",
    "contentType": "image/png"
  }
}

Keep the returned file id. Check that the response contains read_url — if it doesn’t, the image will render as an empty grey box no matter what the rest of the pipeline reports. See files.

Step 2 — Create the poll

{
  "action": "posts:createPoll",
  "params": { "options": ["A course", "A live session", "More templates"] }
}

{ "pollId": "a0169196540f40f082039657d2b17755" }

The poll has no question field. Skool’s poll is only a list of options; the question is the post body you write in the next step. This surprises everyone once.

Step 3 — Create the post

{
  "action": "posts:create",
  "params": {
    "title": "📣 This week in the community",
    "content": "Everything that shipped.\n\nWhat should we build next?",
    "labelId": "your-category-id",
    "attachmentId": "file-id-from-step-1",
    "pollId": "poll-id-from-step-2",
    "notifyAll": true
  }
}

Note the content doubles as the poll question — that’s the design, not a workaround.

Step 4 — Verify by effect, not by status

This is the step people skip, and it’s the one that matters here. Every failure mode in this recipe returns 200:

What can silently go wrong What the API says How to actually tell
The email was never sent 200, post created Check an inbox. The broadcast email’s footer reads “Don’t want admin broadcast emails” — regular notifications say “Don’t want to be notified when…”
The image doesn’t display 200 on all three calls, thumbnails even get generated Open the post. Or check that step 1’s response had read_url
The poll didn’t attach 200, post created Re-fetch the post and confirm pollData.entries[] holds your options
{ "action": "posts:get", "params": { "postId": "id-from-step-3" } }

Production gotchas

notifyAll cannot be added later — ever

Skool offers “email all members” only at creation time. There is no endpoint, and no UI button, to notify about a post that already exists. If you publish without it and then realise you wanted the email, your only option is deleting and re-posting.

Skool does not validate the notify value

Internally this is a query parameter, ?notify=members. Skool accepts any value: send notify=banana and you get 200, a created post, and no email — no error, no warning, nothing in the response to distinguish it from success. If you call api2.skool.com directly rather than through the actor, treat this parameter as a literal you never build from a variable.

Broadcasts are rate-limited to roughly one per 72 hours per group

A second one inside the window returns 400 — notify limit exceeded. Two consequences:

Test in a throwaway community

Every rehearsal of this recipe costs a real broadcast. Create a second Skool group with one or two members and point your test runs there — and hard-code a guard that refuses to run against your production group_id, because the slug is a variable and a copy-paste changes it without anyone noticing.

See also