postiz-app
v2.26.0๐จ The ultimate agentic social media scheduling tool ๐ค
- Maintenance
- Active443 commits in 3 months ยท bus factor 1
- License
- AGPL-3.0Copyleft ยท OSI-approved
- Security
- 23 advisories8 critical, 8 high, 7 medium
- Supported versions
- No scheduleendoflife.date does not track this project
41.6k stars ยท 8k forks ยท 213 open issues ยท last release 1 day ago ยท data checked 4 hours ago
Release history
All releases on GitHubEncrypted channel tokens, connect links, Amazon SES & 40+ fixes
Highlights
This release brings encryption of the channel tokens in the database (opt-in, with a local key or AWS KMS), connect links so the public API or an MCP agent can hand someone a link that adds a channel to your workspace, Amazon SES as an email provider, and a channel checklist in the save and delete dialogs of multi-channel posts. Instagram carousels go up to 20 items and send alt text, and a sweep of billing and provider fixes removes false "may have already published" warnings, duplicate reconnect notifications and several silent billing dead ends.
> [!IMPORTANT]
> This release contains two access-control fixes (billing routes, and post saves across organizations), all users are recommended to upgrade. It also changes the Prisma schema (new indexes only) and adds optional environment variables. No existing Temporal workflow version changed, one new workflow was added. See [Upgrade notes](#upgrade-notes).
๐ Security
- Billing routes require an organization admin โ only the invoices route checked the role, so a plain member could call the others directly (cancel, change the plan, end the trial, request a refund). They now answer 402 with "Only an admin of this organization can do this." (#2421)
- Post saves are scoped to the organization โ a save that carries post or group ids of another organization is refused with a 404 instead of being applied (#2422)
- Channel tokens can be encrypted at rest, see below (#2430)
โจ Features
Token encryption
- Channel tokens encrypted in the database โ with
ENCRYPTION_PROVIDERset, the access and refresh tokens of every channel are stored encrypted (AES-256-GCM). Two providers:local(the key inENCRYPTION_KEY) andaws-kms(a data key that KMS decrypts once at start). Off by default, and nothing changes without it (#2430) - No migration needed: plain and encrypted tokens work side by side, and a plain token is encrypted the next time it is refreshed
- New
encryptTokensWorkflowencrypts the tokens that were stored before, started by hand from the Temporal UI or CLI ENCRYPTION_READ_ONLYfor rollouts where the backend and the orchestrator do not restart at the same moment- Orchestrator activities decrypt the tokens of a channel they receive from a workflow, the groundwork for keeping tokens out of the Temporal history (#2433)
Public API / MCP
- Connect links โ
POST /public/v1/integrations/connectreturns a link, valid for one hour, to a Postiz page where a person (with or without a Postiz account) reads what connecting the channel means and continues to the platform to approve the access. An optionalredirectUrlsends them back to your app afterwards - New MCP tool
integrationConnectTool, so an agent can give the user a link to connect a new channel - Connect links cover the platforms that connect with a plain redirect; platforms that need an API key, an instance URL, a wallet or the browser extension are still added from the dashboard. The channel limit of the plan is checked before the person is sent to the platform
- Amazon SES โ
EMAIL_PROVIDER="ses"withSES_REGION, usingSES_ACCESS_KEY/SES_SECRET_ACCESS_KEYor the default AWS credential chain - Emails carry a
List-Unsubscribeheader that opens the account settings, where email notifications can be turned off (Resend, SES and Nodemailer) - Billing emails: the cancellation email goes to the customer instead of the internal address, and a new set-to-cancel email states the exact end date (#2393, #2394)
App
- Choose channels in the save and delete dialogs โ for a post created for several channels, Delete, Post now, Update / Schedule and Save as draft list the channels with a checkbox each, instead of "all channels / only this channel" (#2422)
- Instagram: carousels of up to 20 media items (#2408), and media alt text is sent as
alt_texton image containers (#2355) - YouTube: the official YouTube icon is used wherever a YouTube channel is shown (YouTube API Services branding requirements), the settings name the Privacy Status and show that the post text is the video description, and the title accepts 1 to 100 characters
- Google My Business: the location picker shows the location address (#2390)
- Sign-up attribution โ a generic attribution provider layer reports new accounts to the configured tools, with LeadJourney as the first provider (
LEADJOURNEY_POSTBACK_URL/LEADJOURNEY_POSTBACK_SECRET) - Optional
RUNPOD_API_URLto point media processing at a local postiz-uploader during development (#2271)
๐ก๏ธ Reliability
- Loading posts no longer blocks the backend on large organizations โ the list is built in one pass and repeating posts are expanded only inside the requested date range, and HTTP keep-alive timeouts now outlast the load balancer's idle timeout, which removes a source of 502s
- Token refresh: a failed refresh no longer stops the refresh of the remaining channels (#2357), a disconnect notifies once instead of once per failed refresh (#2358), and a concurrent refresh is waited for before the channel is flagged for reconnect (#2380)
- The missing-post sweep re-checks the post state before it starts a workflow, and skips deleted posts (#2392)
- Classified provider errors are kept through
runInConcurrentinstead of turning into generic failures (#2344) - Media reads record the real status when a ranged read is not answered with 206 (#2378)
- Short links: the original URL is kept when the short link provider fails (#2417)
- The streak counts in-flight and recurring posts (#2342)
- New indexes for the calendar, notifications, login and admin stats queries
๐ Provider fixes
- LinkedIn: a token rejected on the create call is refreshed and the post is published, instead of stopping with a false "may have already published" warning (#2431); a LinkedIn Page channel that never finished page selection fails fast (#2409)
- Instagram: a post Instagram published despite rejecting
media_publishis recovered (#2361),media_publishis retried when Instagram says the media is not ready (#2410), and standalone connections surface token exchange failures instead of "Not enough scopes" and log the permissions Instagram returned (#2371, #2399) - TikTok: a video chunk upload answered with 5xx or 429 is retried (#2379), and inbox uploads that TikTok reports FAILED after the fact are logged (#2418)
- TikTok Business: fails early when the OAuth redirect has no
auth_code, and "The access token is invalid" is treated as a token error (#2343, #2345) - Threads: post length is counted in characters, not UTF-8 bytes (#2397), and the invalid link attachment error 4279047 is mapped (#2388)
- Pinterest: the token is probed before the pin create is armed (#2360), and the "blocked this link because it may lead to spam" error is mapped (#2398)
- Bluesky: the login identifier is normalized and connect-time errors are mapped (#2367)
- Discord: a bot-token 401 is a post error instead of a reconnect (#2354)
- Tumblr: the public post permalink is returned as the release URL (#2411)
- Slack: the channel picker lists public channels only, and the
groups:readscope is no longer requested - Extension channels reconnect through the extension flow (#2420)
๐ Fixes
- Billing: the cancel flow no longer ends silently at the discount offer (#2419), the finish-trial button says that it charges now (#2416), and plain members are told to ask an admin instead of being sent to the billing page (#2425)
- Stripe: the subscription is always cancelled and the card detached when the trial card check fails (#2402); unpaid invoices of cancelled subscriptions are voided (#2405); the card that pays an unpaid invoice of a cancelled subscription is kept (#2395), and paying such an invoice re-subscribes the customer (#2406)
- Admin: failed refunds are surfaced, and refunds of disputed charges are blocked (#2415)
- Invites: an existing user who accepts an invite by signing in (OAuth or email and password) while logged out is now added to the inviting organization (#1644), and accepting an invite to an organization you already belong to no longer errors (#2368)
- Calendar: the day view no longer crashes when a post's channel is missing (#2389), lists each channel once per slot with stable keys (#2391, #2407), and the calendar keeps a single drag and drop manager (#2356)
- Public API: reconnecting a channel with
GET /public/v1/social/:integration?refresh=works with the integration id, as documented (#2396) - MCP: the Facebook
urlsetting is described in the schema (#2359) - Analytics: neutral empty-state copy that says reconnect instead of refresh (#2424)