Appearance
Plugs
Verified against v1.0.0
Plugs are automation hooks attached to social channel providers. They allow providers to define custom actions that run either on a schedule after publish (auto plugs) or immediately after a post is published (post plugs). Both are configured in the composer's per-channel settings panel.
Two plug types
Auto Plugs (@Plug decorator)
Polling-based background jobs that react to analytics or time-based triggers.
Declared with the @Plug decorator from @postmill-ai/helpers/decorators/plug.decorator:
typescript
@Plug({
identifier: 'republish-top-post',
title: 'Republish top post',
description: 'When a post reaches a certain number of likes, republish it',
runEveryMilliseconds: 3600000, // How often to check
totalRuns: 10, // Max number of runs (0 = unlimited)
disabled: false,
fields: [
{
name: 'likes',
description: 'Number of likes threshold',
type: 'number',
placeholder: '100',
validation: /^\d+$/,
},
],
})Parameters:
identifier— unique plug name.runEveryMilliseconds— polling interval.totalRuns— max runs (0= unlimited).fields— configuration fields the user fills in the UI.
Internal / Post Plugs (@PostPlug decorator)
One-shot actions executed immediately after a post is successfully published.
Declared with the @PostPlug decorator from @postmill-ai/helpers/decorators/post.plug:
typescript
@PostPlug({
identifier: 'repost-as-page',
title: 'Repost as LinkedIn Page',
description: 'After posting, have your LinkedIn Page repost it',
pickIntegration: ['linkedin-page'], // Only show when these providers are connected
fields: [
{
name: 'message',
description: 'Repost message',
type: 'text',
placeholder: 'Check out this post!',
},
],
})Parameters:
identifier— unique plug name.pickIntegration— array of provider identifiers that must be connected for this plug to be available.fields— configuration fields.
How plugs work
Registration
- Providers declare plugs using
@Plugor@PostPlugdecorators on methods. - The decorators store metadata on the prototype via
Reflect.defineMetadata(custom:plugfor auto plugs,custom:internal_plugfor post plugs). IntegrationManager.getAllPlugs()reads this metadata at runtime and returns all available plug definitions.- The frontend fetches
/integrations/plug/listto discover available plugs.
Configuration
Both plug types are configured in the composer's per-channel settings panel (there is no standalone /plugs page). The panel is shown per selected channel and only surfaces the plugs that channel's provider actually declares.
Auto plugs (channel-wide) — the ChannelGlobalPlugs section:
- The frontend fetches
/integrations/plug/listand matches the entry for the channel's provider identifier (renders nothing if the provider declares no auto plugs, or the member lackschannels:update). - It fetches
/integrations/:id/plugsfor existing configurations. - Configuration is saved via
POST /integrations/:id/plugswithPlugDtoand toggled viaPUT /integrations/plugs/:id/activate. - Data is stored in the
PlugsPrisma table, upserted by(plugFunction, integrationId)— so the config is channel-wide and applies to every post that channel publishes.
Post plugs (per-post) — the InternalChannels section, fetched from /integrations/:identifier/internal-plugs. Its values are written into the post's settings JSON (plug--<identifier>--* keys) and travel with that single post.
Execution
Auto Plugs: During the post workflow, PostActivity checks configured plugs. When conditions are met, the plug handler is called.
Post Plugs: Executed by the post-publish Inngest function immediately after a successful provider.post(), before the workflow completes. Idempotency comes from Inngest's durable step.run.
Frontend integration
Both plug surfaces live in the composer's per-channel settings panel (composer/providers/high.order.provider.tsx, portalled into #social-settings):
composer/providers/channel.global.plugs.tsx— the channel-wide auto plugs section, with per-plug cards and a configuration modal built withreact-hook-form+ validation.launches/internal.channels.tsx(InternalChannels) — the per-post post plugs section.
The configuration modal renders fields based on the plug's fields definition (type, placeholder, validation regex).
Plug data flow
Provider @Plug/@PostPlug decorator
→ Reflect metadata ('custom:plug' / 'custom:internal_plug')
→ IntegrationManager.getAllPlugs() reads metadata
→ GET /integrations/plug/list → composer channel-settings panel renders available plugs
→ User configures plug → POST /integrations/:id/plugs
→ Stored in Plugs table (upsert by plugFunction + integrationId)
→ post-publish Inngest function reads plugs during publish
→ Post plugs: run once after provider.post()
→ Auto plugs: scheduled for totalRuns at runEveryMillisecondsSecurity notes
- Plug handlers run with the same credentials as the channel they belong to.
- Outbound HTTP from a plug must go through
safeFetchorthis.fetch(); never use barefetch()on user-influenced URLs. - Post plugs are persisted inside the post
settingsJSON and validated through the same DTO pipeline as other post settings.