S
supero.docs
Documentation/Reference apps/Did the post go out? Amplify, a social media management app, records it

Did the post go out? Amplify, a social media management app, records it

Amplify is an open-source social media management app that can post to four networks and records live or simulated delivery. Source on GitHub, with a live demo.

  • apps
  • marketing
  • integrations
  • workflows
  • saas

Overview

This open-source social media management app enables eight services in config.py, four of them social networks, and keeps three fields on the post that stop the app from overstating what it did.
Press Publish on a post in Amplify and the app stores what happened next. The evidence is on the row. These are the last three attributes of Post in schemas.py:
python
# Real-posting result: did it actually post to the channel, or fall back to simulated?
{"name": "delivery", "type": "string", "values": ["live", "simulated", "failed"]},
{"name": "provider_post_id", "type": "string"},
{"name": "publish_error", "type": "string"},
I searched the config.py and ui/app.js of all 19 apps for the four posting services (instagram, linkedin, x_social, youtube). Only this app lists them. The hosted demo simulates posting, so a Publish there does not reach a real social network. The source is the amplify folder of the supero-apps repo.

A social media management app where four networks post and four are tracked

The platform enum has eight values. ui/app.js names four of them in a constant, REAL_CHANNELS, and a function called realPost maps each of those to an integration call. Instagram needs an image. YouTube needs a public video URL, held in the video_url attribute. For Facebook, TikTok, WhatsApp and Google Ads the function returns null, and the composer labels those channels "tracked only".
What happens next is the part to copy.
publish tries the real call first. If the provider answers with an id or a success status, the row is saved with delivery: "live" and that id in provider_post_id, and if the call throws, or comes back without a confirmation, or realPost has no call for that network, the row is saved with delivery: "simulated" and whatever error message there was goes into publish_error. The pop-up message differs too. A live post gets "Posted to LinkedIn for real". A simulated one is announced as simulated, with a prompt to connect the channel.

An operator and their customers in one tenant

Amplify models a SaaS business, so it has two audiences who never share a screen. The customer is a marketer. The operator is the company running Amplify. Both live in one tenant, a single shared set of records: a customer company is a row in Workspace, and setup.py scopes the marketer (tenant_user) to rows they own.
python
PolicyRule(entity="workspace", can_read=True,
           filter_field="owner_username", filter_match="$user.name"),
The same filter sits on the three working objects (channel, campaign, post) with create and update added. This is policy-based row scoping, and the workspace rule is read-only for the marketer. A customer can see their plan and billing state and cannot write to either.
The operator signs in as tenant_admin and gets a different console, with tabs for billing and for every workspace and campaign on the platform. Its MRR (monthly recurring revenue) tile sums mrr over workspaces whose state is active or past_due. I counted the seed: 4 plans and 6 workspaces, then 10 channels, 8 campaigns, 16 posts. On those rows the tile comes to $456 from four counted workspaces, and the marketer login owns 9 of the 16 posts.
Plan is the one public schema, so the pricing page renders for a logged-out visitor.

Why workflows comes last in the list

config.py enables eight services, and a comment above the list explains their order:
python
services: list = field(default_factory=lambda: ["email", "slack", "ai",
                                                "instagram", "linkedin", "x_social", "youtube",
                                                "workflows"])
The comment's reason is that event bindings are wired up when the workflows service registers, so every other service has to be present first.
There are three workflows and two of them have no button. campaign_launch is bound to @create:amplify:campaign and sends an email plus a message to the #campaigns Slack channel. workspace_welcome is bound to @create:amplify:workspace. The third, post_publish, is a saga that marks the post published, emails the owner and then writes the metrics, and its first step carries a compensate block that sets post_state back to scheduled if a later step fails.

Captions with a fallback you can see

The composer has an AI panel. You type a brief and pick platforms. It calls the ai service once per platform with a short style guide for each, asking for under 280 characters on X and for a hook line with a single call to action on LinkedIn, and when the service is unavailable or returns no text, generateCaption falls back to a template written for that platform.
Each draft card is tagged "AI" or "template", from a flag generateCaption returns with the text.
The front end is a single file, ui/app.js, 840 lines in the repo. You edit it and refresh the page.

Try the marketer, then the operator

Two demo accounts are listed on the login page at amplify.supero.live. Sign in as the marketer and open the Composer and Posts tabs. In the seed, two of the marketer's 9 posts are scheduled and two are drafts. Then sign in as the operator, whose console lists every workspace. The seed has six.
To run it from the source folder:
bash
git clone https://github.com/supero-platform/supero-apps
cd supero-apps/apps/marketing/amplify
cp .env.example .env    # set SUPERO_DOMAIN and SUPERO_PASSWORD
./run.sh
It needs no account and is running in about two minutes on http://localhost:5672. Everything in the folder is MIT-licensed, while the platform underneath is hosted and its source is not open. Until a network's credentials are configured on your own domain, every publish will be recorded as simulated on the row. To build a scheduler around your own channels, the starting point is supero.dev.