Appearance
Platform Channel Apps
Postmill resolves channel (social posting) credentials at two scopes:
- Platform channel apps (this page) — the operator sets one OAuth app per provider in the deployment environment (
.env/ Docker Compose). Every organization then connects that channel with one click — no per-org key entry. - Per-org BYO credentials — an organization adds its own app via Settings → Channels (advanced). A per-org config always wins over the platform env app, so a tenant can bring its own app even when a platform app exists.
When neither exists, the connect dialog falls back to the per-org key form ("alternatively use keys"). Env values are resolved live, per request, and never persisted to a tenant row.
GET /integrations exposes platformConfigured: true for providers with a working env app. Tenants see those providers as one-click Connect with the note "Uses the Postmill app — no setup needed"; providers without a platform app require the tenant's own app via Settings → Channels.
Callback URLs and restarts
The OAuth callback URL for channel connections is always:
https://<your-postmill-domain>/integrations/social/<identifier>(e.g. https://postmill.example.com/integrations/social/facebook). Register exactly this URL in the provider's developer portal. Telegram (bot token) and Wrapcast (client-side Neynar sign-in) are the exceptions — no callback is registered.
Channel env vars enter process.env when the backend boots — restart the backend after editing .env. The frontend needs no rebuild for these variables.
The canonical env-var mapping is CHANNEL_ENV_MAPPINGS in libraries/nestjs-libraries/src/integrations/channel-env-credentials.ts; the table on the Configuration page mirrors it.
Meta — Facebook Pages
One Meta app covers Facebook Pages (facebook) and Instagram Business accounts connected via Facebook login (instagram) — both identifiers read FACEBOOK_APP_ID / FACEBOOK_APP_SECRET.
- Open Meta for Developers and create an app (type Other → Business), or pick an existing one.
- Add the Facebook Login product — on new apps the only option is Facebook Login for Business (FBfB).
- Under App settings → Basic, add your Postmill domain to App Domains.
- Under Facebook Login → Settings, add
https://<your-domain>/integrations/social/facebookandhttps://<your-domain>/integrations/social/instagramto Valid OAuth Redirect URIs. - Copy the App ID and App Secret from App settings → Basic:
yaml
FACEBOOK_APP_ID: '1234567890123456'
FACEBOOK_APP_SECRET: '<your-app-secret>'
FACEBOOK_CONFIG_ID: '<configuration-id>' # FBfB-only apps: see below- Restart the backend.
Scopes vs. Configuration ID. Postmill supports both Meta login modes:
- Classic Facebook Login — Postmill requests these scopes:
pages_show_list,business_management,pages_manage_posts,pages_manage_engagement,pages_read_engagement,read_insights. Instagram-via-Facebook requests its own set:instagram_basic,pages_show_list,pages_read_engagement,business_management,instagram_content_publish,instagram_manage_comments,instagram_manage_insights. - Facebook Login for Business — Meta rejects the
scopeparameter on FBfB-only apps. Instead, create a Configuration under Facebook Login for Business → Configurations (it bundles the token type, assets, and permissions) and set its Configuration ID asFACEBOOK_CONFIG_IDfor the platform app (or paste it into the org's channel form for a BYO app —additionalConfig.configIdin Settings → Channels; the connect flow offers the field). When a Configuration ID is present it replaces the scope list in the OAuth URL.
App Review / going Live. While the app is in development mode only its admins/developers/testers can connect. For production use the app must pass Meta App Review for the permissions above, and Meta requires Deauthorize and Data Deletion callback URLs before an app can go Live — Postmill ships no built-in endpoints for these, so point them at your own privacy/data-deletion pages; check Meta's current docs for the exact requirements.
Instagram Standalone
instagram-standalone uses Instagram's own login (not Facebook login) with a separate credential pair:
- In Meta for Developers, open (or create) your app and add the Instagram product.
- Choose API setup with Instagram login — not "API setup with Facebook login".
- Under Business login settings, add
https://<your-domain>/integrations/social/instagram-standaloneto the OAuth redirect URIs list. - Copy the Instagram App ID and Instagram App Secret from the same page:
yaml
INSTAGRAM_APP_ID: '1234567890123456'
INSTAGRAM_APP_SECRET: '<your-instagram-app-secret>'- Restart the backend.
Postmill requests the scopes instagram_business_basic, instagram_business_content_publish, instagram_business_manage_comments, and instagram_business_manage_insights. The connected Instagram account must be a professional (Business or Creator) account.
While the app is in development mode, each Instagram account that will connect must be added as an Instagram tester on this app: Meta App Dashboard → your app → App roles → Roles → Add People, choose Instagram Tester, and enter the account's Instagram handle. The invited user must then accept the invitation: Instagram app → Settings → Apps and Websites → Tester Invites (on the web: https://www.instagram.com/accounts/manage_access/ → Tester Invites). The tester must be added on the app whose ID is in INSTAGRAM_APP_ID — an accepted invite on a different Meta app does not count. Without this, OAuth fails with "Insufficient Developer Role" (or "Invalid platform app"). To remove the requirement entirely, switch the app to Live mode (App Review → go live).
Note: this adapter enforces that FRONTEND_URL is a public HTTPS origin — private/loopback origins are rejected for OAuth redirects.
X (Twitter)
- Open the X Developer Portal and create a project + app (or pick an existing one).
- Under User authentication settings, set Type of App to Web App, Automated App or Bot and enable OAuth 1.0a with Read and write permissions. The channel flow is OAuth 1.0a with write access (no granular scopes).
- Add
https://<your-domain>/integrations/social/xto the allowed Callback URIs. - Copy the API Key and API Secret from Keys and tokens → Consumer Keys:
yaml
X_API_KEY: '<your-api-key>'
X_API_SECRET: '<your-api-secret>'- Restart the backend.
Posting requires an X access tier with write access — check X's current tier docs for Free-tier limits. (The adapter throttles itself to one concurrent posting job, citing X's ~300-posts-per-3-hours rate ceiling.)
SSO caveat: X login uses a separate OAuth 2.0 + PKCE flow with only the users.read scope, which returns no email address. X SSO accounts get a synthetic address (x_<id>@x.login.postmill.local) and are skipped by newsletter/welcome email — see SSO dual-use.
LinkedIn
The LinkedIn (linkedin) personal channel reads LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET.
- Open the LinkedIn Developer Portal and create an app, linked to a LinkedIn Page you admin.
- On the Products tab, request access to Share on LinkedIn and Sign In with LinkedIn using OpenID Connect. Neither product requires an app review.
- On the Auth tab, under OAuth 2.0 settings, add
https://<your-domain>/integrations/social/linkedinto Authorized redirect URLs for your app. - Copy the Client ID and Primary Client Secret from the Auth tab:
yaml
LINKEDIN_CLIENT_ID: '77a1b2c3d4e5f6g7'
LINKEDIN_CLIENT_SECRET: '<your-client-secret>'- Restart the backend.
The personal adapter requests openid, profile, and w_member_social only.
LinkedIn Page (linkedin-page) cannot share the personal app: it requests the organization scopes (rw_organization_admin, w_organization_social, r_organization_social), which only come with the Community Management API product — and LinkedIn requires that product to be the ONLY product on the app. Create a second, dedicated app:
- Open the LinkedIn Developer Portal and create a NEW app (separate from the personal one above), linked to the LinkedIn Page you want to post to.
- On the Products tab, request access to Community Management API — and nothing else. Do NOT add Share on LinkedIn or Sign In with LinkedIn; LinkedIn rejects the page-channel OAuth flow when the app carries any other product.
- On the Settings tab, under Verification, verify the app against the linked Page: generate the verification URL and open it while signed in as a super admin of that Page. The organization scopes stay inert until the app is verified.
- On the Auth tab, under OAuth 2.0 settings, add
https://<your-domain>/integrations/social/linkedin-pageto Authorized redirect URLs for your app. - Copy the Client ID and Primary Client Secret from the Auth tab — the page app reads its OWN env pair (
linkedin-pagenever falls back to the personal app's keys):
yaml
LINKEDIN_PAGE_CLIENT_ID: '<dedicated-page-app-client-id>'
LINKEDIN_PAGE_CLIENT_SECRET: '<dedicated-page-app-client-secret>'- Restart the backend.
The page adapter requests openid, profile, w_member_social, r_basicprofile, rw_organization_admin, w_organization_social, and r_organization_social. Connecting is two steps: after OAuth, the tenant picks which of their administered Pages to post to. With the pair unset, orgs can still enter their own page app per credential set (Advanced → use your own app).
Telegram
Telegram is token-only: the platform's bot token is the entire credential.
- In Telegram, start a chat with @BotFather.
- Send
/newbotand follow the prompts (or reuse an existing bot via/mybots). - Copy the API token BotFather gives you:
yaml
TELEGRAM_TOKEN: '123456789:AAEhBOweik6ad9r_QXMENQjcrGbqCr4K-4rI'- Restart the backend.
Tenant flow: the tenant opens the Telegram connect dialog, adds your bot to their channel or group (as an admin so it can post), then posts the /connect <code> message the dialog shows, addressed to the bot (e.g. /connect a1b2c3d4 as a message to @YourPostmillBot). Postmill picks the message up from the bot's updates, links that chat to the tenant's channel, and — when the bot has admin rights with message-delete permission — deletes the /connect message and its own confirmation.
LINE
LINE is token-only (same pattern as Telegram): the platform's Messaging API channel access token is the entire credential.
- Open the LINE Developers console and create a provider (or pick an existing one).
- Create a Messaging API channel on it — this also creates the LINE Official Account users will connect to.
- On the channel's Messaging API tab, issue a channel access token (long-lived):
yaml
LINE_CHANNEL_ACCESS_TOKEN: '<your-long-lived-channel-access-token>'- Restart the backend.
Tenant flow: the tenant pastes no keys — they add your LINE Official Account as a friend (or to a group) and connect with one click. Posts are sent as bot broadcast messages to every friend of the account. Note that LINE broadcast responses carry no message id, so post permalinks point at the LINE Official Account Manager.
Google — YouTube
- Open the Google Cloud Console and create a project (or pick an existing one).
- Under APIs & Services → Library, enable the YouTube Data API v3 and the YouTube Analytics API.
- Configure the OAuth consent screen (external) and add the scopes Postmill requests:
userinfo.profile,userinfo.email,youtube,youtube.force-ssl,youtube.readonly,youtube.upload,youtubepartner, andyt-analytics.readonly. While the consent screen is in "Testing" mode, add each connecting Google account as a test user. - Create an OAuth 2.0 Client ID of type Web application and add
https://<your-domain>/integrations/social/youtubeto Authorized redirect URIs. - Copy the Client ID and Client Secret:
yaml
YOUTUBE_CLIENT_ID: '1234567890-abcd1234.apps.googleusercontent.com'
YOUTUBE_CLIENT_SECRET: '<your-client-secret>'- Restart the backend.
These same variables dual-use as Google SSO login — setting them puts a "Sign in with Google" button on the login page (see OAuth / SSO). Because several of the YouTube scopes are sensitive/restricted, Google requires app verification before the consent screen can serve the general public in production — check Google's current verification requirements.
Google Business Profile (GMB) is a separate credential pair and callback; enable the Google Business Profile APIs on a project and register https://<your-domain>/integrations/social/gmb (scopes: userinfo.profile, userinfo.email, business.manage):
yaml
GOOGLE_GMB_CLIENT_ID: '<your-gmb-client-id>'
GOOGLE_GMB_CLIENT_SECRET: '<your-gmb-client-secret>'Other env-mapped providers
Index of every env-mapped provider — each has a full setup section further down this page (same format as Meta / X / LinkedIn / Google above). Portal URLs, callback paths, and the scopes each section lists are taken from the provider's adapter; env vars from CHANNEL_ENV_MAPPINGS.
One rule covers all of them: each provider's section lists the exact scopes Postmill requests at connect time — request precisely these in the app review. Postmill validates the granted scope set when a tenant connects and refuses the connection if any scope is missing (e.g. TikTok's scope_not_authorized), so an app approved for fewer scopes than its section lists will connect for no one.
| Identifier | Developer portal | Env vars | Callback path |
|---|---|---|---|
TikTok (tiktok) | developers.tiktok.com/apps | TIKTOK_CLIENT_ID / TIKTOK_CLIENT_SECRET | /integrations/social/tiktok |
Pinterest (pinterest) | developers.pinterest.com/apps | PINTEREST_CLIENT_ID / PINTEREST_CLIENT_SECRET | /integrations/social/pinterest |
Reddit (reddit) | reddit.com/prefs/apps | REDDIT_CLIENT_ID / REDDIT_CLIENT_SECRET | /integrations/social/reddit |
Twitch (twitch) | dev.twitch.tv/console/apps | TWITCH_CLIENT_ID / TWITCH_CLIENT_SECRET | /integrations/social/twitch |
Threads (threads) | developers.facebook.com/apps | THREADS_APP_ID / THREADS_APP_SECRET | /integrations/social/threads |
Discord (discord) | discord.com/developers/applications | DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRET | /integrations/social/discord |
Slack (slack) | api.slack.com/apps | SLACK_ID / SLACK_SECRET | /integrations/social/slack |
Dribbble (dribbble) | dribbble.com/account/applications/new | DRIBBBLE_CLIENT_ID / DRIBBBLE_CLIENT_SECRET | /integrations/social/dribbble |
Kick (kick) | kick.com/settings/developer | KICK_CLIENT_ID / KICK_SECRET | /integrations/social/kick |
VK (vk) | id.vk.com/about/business/go | VK_ID (id only, no secret) | /integrations/social/vk |
Whop (whop) | whop.com/dashboard/developer | WHOP_CLIENT_ID (id only, PKCE) | /integrations/social/whop |
MeWe (mewe) | dev.mewe.com | MEWE_APP_ID / MEWE_API_KEY | /integrations/social/mewe |
Mastodon (mastodon) | per-instance (see below) | MASTODON_CLIENT_ID / MASTODON_CLIENT_SECRET | /integrations/social/mastodon |
Wrapcast / Farcaster (wrapcast) | dev.neynar.com | NEYNAR_CLIENT_ID / NEYNAR_SECRET_KEY | none — client-side "Sign in with Farcaster" (Neynar) |
LINE (line) | developers.line.biz/console | LINE_CHANNEL_ACCESS_TOKEN (token only) | none — token-only (see the LINE section above) |
Custom OAuth (oauth_custom) | your own OIDC provider | POSTMILL_OAUTH_CLIENT_ID / POSTMILL_OAUTH_CLIENT_SECRET | see note below |
TikTok
TikTok needs two products added to the app, and the connect flow requests a six-scope set that must be approved in full — Postmill validates the granted scopes when a tenant connects and refuses the connection if any is missing (scope_not_authorized).
- Open TikTok for Developers → My Apps and click Create an app.
- On the app's page, add the products Login Kit and Content Posting API.
- Under Login Kit settings, add the redirect URI
https://<your-domain>/integrations/social/tiktok. - In the app review, request all six scopes:
video.list,user.info.basic,video.publish,video.upload,user.info.profile,user.info.stats.video.upload/video.publishare the scheduled-posting flows (upload-to-drafts and direct publish);user.info.basicidentifies the connected account;user.info.profile/user.info.statsfeed channel analytics;video.listfeeds post analytics. - Copy the Client Key and Client Secret:
yaml
TIKTOK_CLIENT_ID: '<your-client-key>'
TIKTOK_CLIENT_SECRET: '<your-client-secret>'- Restart the backend.
Before the app passes review the production credentials are dead — while the app sits in Draft or in review, the OAuth login fails on TikTok's side with a generic "client_key — we couldn't log you in" error. To test before approval:
- On the app's page open the Sandbox section and use the sandbox Client Key / Client Secret as
TIKTOK_CLIENT_ID/TIKTOK_CLIENT_SECRET(swap the production pair back in once the app is approved). - Under Sandbox → Target Users, click Add Account and log in with each TikTok account that will connect. If the TikTok account is registered to a phone number rather than an email, invite by scanning the QR code shown there with the TikTok mobile app.
- An unaudited (unapproved) app may only direct-post to private TikTok accounts (
unaudited_client_can_only_post_to_private_accounts). Flip the test account to private in the TikTok app (Settings → Privacy) while testing; approved apps have no such restriction. Sandbox posts land asSELF_ONLYvideos visible only to the account owner.
The redirect URI always goes under Login Kit settings (step 3), for both the production and sandbox apps.
Discord
Discord OAuth identifies the user; posting runs on a bot token, so both pieces live on the same Discord application.
- Open the Discord Developer Portal and click New Application.
- Under OAuth2 → General, add the redirect URL
https://<your-domain>/integrations/social/discordand copy the Client ID and Client Secret:
yaml
DISCORD_CLIENT_ID: '<your-client-id>'
DISCORD_CLIENT_SECRET: '<your-client-secret>'- Under Bot, create the bot user and copy its token (click Reset Token). The platform app carries it in the environment — every channel list and post on every org authenticates with it:
yaml
DISCORD_BOT_TOKEN: '<your-bot-token>'(Tenants who bring their own Discord app instead paste the bot token in the channel's settings in Postmill — org credential discord.token.) 4. Restart the backend.
Tenant flow: the tenant authorizes the platform bot into their guild during channel connect (the OAuth screen asks for a server) — the bot needs write access to the target channel. No manual URL-generator step is needed with the platform app.
Dribbble
- Open Dribbble Applications and register a new application with the callback URL
https://<your-domain>/integrations/social/dribbble. The app requests thepublicanduploadscopes. - Copy the Client ID and Client Secret:
yaml
DRIBBBLE_CLIENT_ID: '<your-client-id>'
DRIBBBLE_CLIENT_SECRET: '<your-client-secret>'- Restart the backend.
Kick
- Open Kick Developer Settings and create a new application.
- Add the redirect URL
https://<your-domain>/integrations/social/kick. The app requests the scopeschat:write,user:read,channel:read. - Copy the Client ID and Client Secret:
yaml
KICK_CLIENT_ID: '<your-client-id>'
KICK_SECRET: '<your-client-secret>'- Restart the backend.
Mastodon
Mastodon normally needs no app at all: tenants type their instance hostname in the connect dialog and Postmill registers itself on that server automatically (dynamic client registration). The same per-instance flow powers the GoToSocial, Akkoma, and Friendica channels (Mastodon-API servers) and, with Misskey's MiAuth instead of client registration, the Misskey and Sharkey channels — none of them need env vars.
Operators who prefer a fixed pre-registered app can set:
yaml
MASTODON_CLIENT_ID: '<your-client-id>'
MASTODON_CLIENT_SECRET: '<your-client-secret>'MeWe
MeWe hands out API access per developer application (App ID + API Key); the connect flow redirects to the MeWe login with the registered redirect_uri.
- Open MeWe Developers and create an application.
- Register the redirect URL
https://<your-domain>/integrations/social/mewe. - Copy the App ID and API Key:
yaml
MEWE_APP_ID: '<your-app-id>'
MEWE_API_KEY: '<your-api-key>'- Restart the backend.
Pinterest
- Open Pinterest Developers → My Apps and Create app.
- Add the redirect URI
https://<your-domain>/integrations/social/pinterest. The app requests the scopesboards:read,boards:write,pins:read,pins:write,user_accounts:read. - Trial access covers development; apply for Standard access (a short demo video of the OAuth flow + a pin being created) before serving the general public. On Trial, production pin creation is blocked (API error 29). To test posting end-to-end before approval, point the data plane at Pinterest's API Sandbox — the sandbox has its OWN tokens (generate one in the developer portal; production OAuth tokens are rejected there), so this is a testing escape hatch, not a tenant flow. OAuth and identity always stay on production:
yaml
PINTEREST_API_BASE: 'https://api-sandbox.pinterest.com'- Copy the App ID and App Secret:
yaml
PINTEREST_CLIENT_ID: '<your-app-id>'
PINTEREST_CLIENT_SECRET: '<your-app-secret>'- Restart the backend.
Reddit
- Open Reddit Apps and click create app — choose type web app.
- Set the redirect URI to
https://<your-domain>/integrations/social/reddit. The app requests the scopesread,identity,submit,flair. - Copy the client ID (shown under the app name) and the secret:
yaml
REDDIT_CLIENT_ID: '<your-client-id>'
REDDIT_CLIENT_SECRET: '<your-client-secret>'- Restart the backend.
Slack
- Open Slack API: Your Apps and Create New App → From scratch.
- Under OAuth & Permissions, add the redirect URL
https://<your-domain>/integrations/social/slackand the bot token scopeschannels:read,chat:write,users:read,groups:read,channels:join,chat:write.customize. - Under Basic Information, copy the Client ID and Client Secret:
yaml
SLACK_ID: '<your-client-id>'
SLACK_SECRET: '<your-client-secret>'- Restart the backend.
Tenant flow: the tenant installs the app into their workspace from the connect button. For workspaces other than the app's own, enable Manage Distribution → Activate Public Distribution in the Slack app, or installs are limited to the creating workspace.
Threads
Threads lives on a Meta app with the Threads use case — separate env vars from the Facebook Pages credentials.
- Open Meta for Developers → My Apps and create an app (or reuse an existing one).
- Add the Threads API use case and the permissions
threads_basic,threads_content_publish,threads_manage_replies,threads_manage_insights. - Add the OAuth redirect URI
https://<your-domain>/integrations/social/threads. - Copy the Threads App ID and App Secret:
yaml
THREADS_APP_ID: '<your-threads-app-id>'
THREADS_APP_SECRET: '<your-threads-app-secret>'- Restart the backend. Advanced access (beyond tester accounts) requires Meta App Review like the other Meta products.
While the app is in development mode, each Threads account that will connect must be added as a Threads tester on this app: Meta App Dashboard → your app → App roles → Roles → Add People, choose Threads Tester, and enter the account's Threads handle. The invited user must then accept the invitation in the Threads app: Settings → Account → Website permissions → Invites. The tester must be added on the app whose ID is in THREADS_APP_ID — an accepted invite on a different Meta app does not count. Without this, OAuth fails at authorize or token exchange. To remove the requirement entirely, switch the app to Live mode (App Review → go live).
Twitch
- Open the Twitch Developer Console and Register Your Application.
- Add the OAuth redirect URL
https://<your-domain>/integrations/social/twitch. The app requests the scopesuser:write:chat,user:read:chat,moderator:manage:announcements. - Copy the Client ID and Client Secret (under Manage):
yaml
TWITCH_CLIENT_ID: '<your-client-id>'
TWITCH_CLIENT_SECRET: '<your-client-secret>'- Restart the backend.
VK
VK uses a VK ID application with PKCE — only the Client ID, no secret.
- Open the VK ID Console and create an application (Web).
- Add the authorized redirect URI
https://<your-domain>/integrations/social/vk. The app requests the scopesvkid.personal_info,email,wall,status,docs,photos,video. - Copy the Application ID:
yaml
VK_ID: '<your-app-id>'- Restart the backend.
Whop
Whop OAuth uses PKCE with only the Client ID (no client secret).
- Open the Whop Developer Dashboard and create an app.
- Add the redirect URI
https://<your-domain>/integrations/social/whop. The app requests the scopesopenid,profile,email,forum:post:create,forum:read,company:basic:read. - Copy the Client ID:
yaml
WHOP_CLIENT_ID: '<your-client-id>'- Restart the backend.
Wrapcast (Farcaster)
Wrapcast uses a Neynar app: sign-in happens client-side in the composer ("Sign in with Farcaster") — no callback to register.
- Open Neynar and create an app.
- Copy the Client ID and API Key:
yaml
NEYNAR_CLIENT_ID: '<your-neynar-client-id>'
NEYNAR_SECRET_KEY: '<your-neynar-api-key>'- Restart the backend.
Tenant flow: the tenant clicks "Sign in with Farcaster" in the composer connect flow; publishing uses the app's API key server-side.
Custom OAuth
Custom OAuth channels reuse the generic-OIDC variables; there is no dedicated provider adapter in this repo — check the provider's current docs for the callback to register.
yaml
POSTMILL_OAUTH_CLIENT_ID: '<your-oidc-client-id>'
POSTMILL_OAUTH_CLIENT_SECRET: '<your-oidc-client-secret>'SSO dual-use (login with the same app)
Three channel apps can double as login providers on the auth page. The gate is an opt-in flag plus the matching channel credential vars — the login page never advertises a provider whose channel app is unconfigured:
| Flag | Requires | Login flow |
|---|---|---|
FACEBOOK_SSO_ENABLED: 'true' | FACEBOOK_APP_ID / FACEBOOK_APP_SECRET | Facebook OAuth (public_profile,email) |
X_SSO_ENABLED: 'true' | X_API_KEY / X_API_SECRET | X OAuth 2.0 + PKCE (users.read) |
LINKEDIN_SSO_ENABLED: 'true' | LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET | LinkedIn OIDC (openid profile email) |
Provider-specific prerequisites:
- Facebook SSO additionally needs the consumer Facebook Login product on the Meta app — the FBfB Configuration-ID flow is Pages-only and cannot log users in. If the user denies the email permission, Postmill mints
fb_<id>@facebook.login.postmill.local. - X SSO accounts always get a synthetic address (
x_<id>@x.login.postmill.local) — X returns no email. - LinkedIn SSO needs the Sign In with LinkedIn using OpenID Connect product enabled (same product the channel flow already requires).
Synthetic .login.postmill.local addresses are skipped by newsletter enrollment and welcome emails.
No account linking: each SSO login matches users by (provider, provider-user-id) only. If someone signs in with Facebook and later with Google under the same real email, Postmill provisions separate User + Org accounts — identities are never merged.
Full walkthrough: OAuth / SSO.
Direct-auth providers (no platform app needed)
These channels need no developer app at either scope — tenants enter account credentials (or an instance hostname) directly in the connect dialog: Bluesky (app password), Mastodon (instance hostname), GoToSocial, Akkoma, Friendica, Misskey, Sharkey (instance hostname), Matrix (homeserver + access token + room ID), Discourse (API key), Odysee (self-hosted lbrynet daemon — advanced, see the channel's setup steps), PeerTube, Skool, Hashnode, Medium, WordPress, Nostr, Lemmy, Pixelfed, dev.to, Listmonk, and Moltbook.
Related
- Configuration — the full env var table
- OAuth / SSO — login providers, callback routing, registration policy
Verified against v1.0.0