$npx -y skills add MoizIbnYousaf/marketing-cli --skill postizSchedule social posts to any of 30+ providers via a running postiz instance (hosted at api.postiz.com or self-hosted). Use this skill whenever someone wants to post, schedule, or publish a draft to LinkedIn, Reddit, Bluesky, Mastodon, Threads, Instagram, TikTok, YouTube, Pinteres
| 1 | # Postiz — Social Distribution Layer |
| 2 | |
| 3 | You schedule pre-written social posts to LinkedIn, Reddit, Bluesky, Mastodon, Threads, Instagram, TikTok, YouTube, Pinterest, Discord, Slack, and any other provider a running postiz instance supports. You do NOT write copy. You do NOT tailor per platform. Upstream skills (content-atomizer, social-campaign) deliver platform-specific text; you turn it into drafts in postiz. |
| 4 | |
| 5 | For Twitter/X threads, defer to `typefully` — its thread UX is canonical. You handle everything else. |
| 6 | |
| 7 | ## North Star |
| 8 | |
| 9 | Postiz is a distribution execution layer in the mktg playbook: |
| 10 | |
| 11 | 1. You never generate or rewrite copy. |
| 12 | 2. You never choose the platform mix — that's the user's ask or social-campaign's plan. |
| 13 | 3. You never retry a failed post without first consulting the sent-marker file at `.mktg/publish/{campaign}-postiz.json` — postiz has no idempotency key, and retries duplicate. |
| 14 | 4. You always use `mktg publish --adapter postiz` — never call the postiz API directly from the skill. The adapter owns rate-limit handling, 401/429 envelopes, and the two-step integration.id resolution. |
| 15 | |
| 16 | ## On Activation |
| 17 | |
| 18 | Run these 4 steps before anything else. Each step has a fallback that keeps the skill useful even when postiz is absent. |
| 19 | |
| 20 | ### Step 1 — Verify the catalog is registered and configured |
| 21 | |
| 22 | ```bash |
| 23 | mktg catalog info postiz --json --fields configured,missing_envs,auth.credential_envs,resolved_base |
| 24 | ``` |
| 25 | |
| 26 | `catalog info <name>` returns the full `CatalogEntry` plus computed `configured`/`missing_envs`/`resolved_base` fields. The command errors with exit code 1 (NOT_FOUND) if the catalog is not in `catalogs-manifest.json` — that's the "not registered" signal. |
| 27 | |
| 28 | - Exit code 1 → postiz catalog is not registered. Tell the user: `mktg catalog add postiz --confirm`. Stop. |
| 29 | - `configured: false` → env vars are missing. Build the fix string from `missing_envs` rather than hardcoding. Canonical expectation: `POSTIZ_API_KEY` (required) and `POSTIZ_API_BASE` (defaults to `https://api.postiz.com` if unset; the catalog loader returns `resolved_base: null` in that case and the skill applies the default). Stop. |
| 30 | - `configured: true` → proceed to Step 2. |
| 31 | |
| 32 | ### Step 2 — Resolve connected provider identifiers (cached) |
| 33 | |
| 34 | ```bash |
| 35 | mktg publish --adapter postiz --list-integrations --json |
| 36 | ``` |
| 37 | |
| 38 | Returns `{ adapter, integrations: [{ id, identifier, name, profile, disabled, picture, customer }, ...] }` from `GET /public/v1/integrations`. Cache in `.mktg/cache/postiz-integrations.json` (TTL 15 minutes). Use this to validate the user's requested providers before publish — fail fast if they ask for `"pinterest"` and it isn't connected. |
| 39 | |
| 40 | **Critical: `identifier` is a postiz-native provider-module key, NOT a platform display name.** Single-variant providers collapse to the expected string (`"reddit"`, `"bluesky"`, `"mastodon"`, `"threads"`), but some platforms ship multiple identifiers: |
| 41 | |
| 42 | | Platform | Possible postiz `identifier` values | |
| 43 | |---|---| |
| 44 | | LinkedIn | `linkedin` (personal profile), `linkedin-page` (company page) — distinct integrations | |
| 45 | | YouTube | may vary by postiz version (channel vs shorts flavors) | |
| 46 | | Other | any string postiz's provider module exports | |
| 47 | |
| 48 | The adapter does **exact-match** on `identifier` (no alias layer) — if the user says *"post to LinkedIn"* and the instance only has `linkedin-page` connected, the adapter will refuse `"linkedin"`. Alias handling is your job in Step 2b. |
| 49 | |
| 50 | ### Step 2b — Handle user friendly-name aliasing (skill-layer only) |
| 51 | |
| 52 | When the agent hears *"post to LinkedIn"* (a platform name, not a postiz identifier), map the friendly name to the right postiz identifier(s): |
| 53 | |
| 54 | 1. Look up the cached integrations from Step 2. |
| 55 | 2. Find all identifiers whose display name or known-alias matches the user's phrase. For LinkedIn, both `linkedin` and `linkedin-page` match. |
| 56 | 3. If exactly one matches → use it. |
| 57 | 4. If multiple match → prese |