Skip to content

Configuration ​

Every environment variable Postmill recognises, sourced from .env.example. All variables are read at boot time. Most feature-specific provider credentials (channel OAuth apps, AI providers, storage, short links) are configured per-organization in-app; this page covers the deployment-level variables.

Required ​

VariableDefaultPurpose
DATABASE_URL—PostgreSQL connection string for the application database
REDIS_URL—Redis connection string. Use redis:// for local Redis or rediss:// for Upstash / TLS endpoints
JWT_SECRET—Secret key for signing JWT tokens; also used as the encryption key fallback
FRONTEND_URL—Public-facing URL of the application (e.g. https://postmill.example.com)
NEXT_PUBLIC_BACKEND_URL—Public URL of the backend API (e.g. https://postmill.example.com/api)
BACKEND_INTERNAL_URL—Internal URL for backend-to-backend calls (e.g. http://localhost:3000)
MAIN_URL—Alternative public URL, used in Docker Compose alongside FRONTEND_URL
IS_GENERAL—Must be true for standard self-hosted deployments

Storage ​

VariableDefaultPurpose
UPLOAD_DIRECTORY—Local path for file uploads (e.g. /uploads). Avatars and app-internal images always use LOCAL
NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY—Public URL path serving uploads (e.g. /uploads)
MEDIA_UPLOAD_MAX_BYTES1073741824Maximum upload file size for /files/upload-server (default 1 GB)
LOCAL_STORAGE_QUOTA_GB5Default soft quota for each org's local storage, in GB

Email ​

Pluggable provider system with 6 adapters: Resend, SendGrid, Mailgun, Postmark, Amazon SES, and SMTP. The active provider is selected globally via EMAIL_PROVIDER; unset/unknown → email is off (users activate automatically).

VariableDefaultPurpose
EMAIL_PROVIDER—resend, sendgrid, mailgun, postmark, ses, or smtp
EMAIL_API_KEY—API key for Resend, SendGrid, Mailgun, or Postmark
EMAIL_FROM_ADDRESS—Sender email address
EMAIL_FROM_NAME—Sender display name
EMAIL_WEBHOOK_SECRET—Signing secret / verification key for the active provider's webhook
EMAIL_MAILGUN_DOMAIN—Mailgun sending domain (required for mailgun)
EMAIL_REGIONusAPI region for Mailgun (us/eu) and SES region
EMAIL_SES_ACCESS_KEY_ID—SES IAM access key (falls back to AWS_* env vars)
EMAIL_SES_SECRET_ACCESS_KEY—SES IAM secret key (falls back to AWS_* env vars)
EMAIL_SMTP_HOST—SMTP server hostname (required for smtp)
ADMIN_NOTIFICATIONS_EMAIL—Operator address that receives one alert per new tenant signup (the first/primary user who creates an org). Unset = no admin alerts
EMAIL_SMTP_PORT587SMTP server port
EMAIL_SMTP_SECUREfalseUse TLS for SMTP
EMAIL_SMTP_USER—SMTP authentication username (optional for open relays)
EMAIL_SMTP_PASS—SMTP authentication password
EMAIL_LOG_RETENTION_DAYS90Days to keep email log metadata before pruning
DISABLE_REGISTRATIONfalseSet to true to disable self-registration. The first user of an empty instance can still register, and GENERIC OIDC sign-ins still provision
DISALLOW_PLUSfalseSet to true to reject email/password (LOCAL) registrations whose address contains a +

The webhook endpoint is at POST /webhooks/email, signature-verified, and registered outside CSRF (same as Stripe). SES uses SNS topic verification; EMAIL_WEBHOOK_SECRET can optionally hold the expected SNS TopicArn.

Provider credential setup ​

ProviderAPI key sourceWebhook signing secret
ResendCreate an API key at resend.comIn webhook settings on the Resend dashboard
SendGridCreate a "Full Access" API key at sendgrid.com/settings/api_keysEnable Event Webhook and copy the Verification Key
MailgunUse SMTP credentials or create a Mailgun API keySent via the Mailgun webhook setup page. Also set EMAIL_MAILGUN_DOMAIN and EMAIL_REGION
PostmarkCreate a server API token at postmarkapp.comGenerated when creating a webhook in the server settings
Amazon SESIAM credentials, or fall back to AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKENSES uses SNS. Subscribe POST /webhooks/email to the SNS topic; EMAIL_WEBHOOK_SECRET can pin the TopicArn
SMTPNo API key — set host/port/secure/user/passWebhooks are not supported

Newsletter signups ​

New registrants can be subscribed to a marketing newsletter list. Beehiiv is used when BEEHIIVE_API_KEY is set, otherwise Listmonk when LISTMONK_API_KEY is set; with neither set, no signup is sent.

VariableDefaultPurpose
BEEHIIVE_API_KEY—Beehiiv API key
BEEHIIVE_PUBLICATION_ID—Beehiiv publication ID the subscriber is added to
LISTMONK_API_KEY—Listmonk API key (HTTP Basic password, paired with LISTMONK_USER)
LISTMONK_USER—Listmonk API username (HTTP Basic user)
LISTMONK_DOMAIN—Base URL of the Listmonk instance. A self-hosted Listmonk on a private network must be allowlisted via SSRF_ALLOWED_PRIVATE_CIDRS
LISTMONK_LIST_ID—Numeric list ID to subscribe new registrants to
LISTMONK_WELCOME_TEMPLATE_ID—Numeric transactional template ID for the welcome email

Campaign Hub ​

VariableDefaultPurpose
CAMPAIGN_PURGE_DAYS30Days after a campaign's endDate before its CampaignItem tags are purged

Push notifications ​

Browser and mobile push notifications are sent via Firebase Cloud Messaging (FCM) v1. Push is globally disabled when any of these is unset.

VariableDefaultPurpose
FCM_PROJECT_ID—Firebase project ID
FCM_CLIENT_EMAIL—Firebase service account client email
FCM_PRIVATE_KEY—Firebase service account private key (PEM)

Analytics & background jobs ​

VariableDefaultPurpose
USE_INNGEST—Set to true to enable Inngest-driven background jobs
INNGEST_EVENT_KEY—Inngest Cloud event key (required unless INNGEST_DEV=1)
INNGEST_SIGNING_KEY—Inngest Cloud signing key (required unless INNGEST_DEV=1)
INNGEST_SIGNING_KEY_FALLBACK—Optional fallback signing key for rotation
INNGEST_ENV—Optional branch environment name
INNGEST_DEV—Set to 1 to use the local Inngest dev server
INNGEST_BASE_URLhttp://localhost:8288Local dev server URL (only used with INNGEST_DEV=1)
INNGEST_SERVE_ORIGIN—Public backend origin served to Inngest
INNGEST_SERVE_PATH/api/inngestPath where the backend serves the Inngest handler
ANALYTICS_DAILY_RETENTION_DAYS548Days to keep daily channel snapshots before rolling up to weekly
ANALYTICS_POST_RETENTION_DAYS90Days to keep per-post snapshots before pruning
ANALYTICS_ANOMALY_Z3Z-score threshold for anomaly (spike/drop) detection in the daily sweep
ANALYTICS_ANOMALY_COOLDOWN_DAYS3Suppress repeat anomaly notifications for the same (channel, metric) for this many days
COMMENTS_SWEEP_INTERVAL_MINUTES30Minutes between comment collection sweeps
POST_DAYS_BACK30Days back to look for posts when fetching comments
SOCIAL_COMMENT_RETENTION_DAYS90Days before social comments are soft-deleted
AGENT_DIGEST_ENABLED—Set to true to enable the Monday 07:00 ET headless AI digest

AI Budget Enforcement ​

AI budgets are configured per-organization in Settings → AI: an organization-wide ceiling (monthly/daily caps + alert threshold, from the card at the top of Settings → AI → LLM Providers or PUT /settings/ai/budget) and per-provider caps. Once a cap is reached, AI calls are refused with HTTP 429 (org_budget_exceeded for the organization-wide ceiling). The deployment-level kill switch below controls whether the caps are actually enforced.

VariableDefaultPurpose
AI_PROVIDER_BUDGET_ENFORCEtrue (unset = on)When true, BudgetService.checkBudget enforces the organization-wide ceiling (Organization.aiBudget*) and the per-provider monthly/daily caps from AIOrgProviderConfig. When false, budget checks always allow the call (alerts and spend logging continue). Set to false to roll back enforcement during an incident without losing cap configuration or alerts.

API ​

VariableDefaultPurpose
API_LIMIT600Public API rate limit per hour
OPENAI_APP_CHALLENGE—Challenge string for OpenAI apps, served at /.well-known/openai-apps-challenge
MOBILE_APP_SCHEMEpostmill://auth/callbackDeep-link scheme the mobile OAuth callback (GET /auth/oauth-mobile-callback) redirects to
NEXT_PUBLIC_OVERRIDE_BACKEND_URL—Overrides the token endpoint advertised in the MCP OAuth discovery document (/.well-known/oauth-authorization-server); falls back to the backend URL
AGENT_MEDIA_SSO_KEY—JWT signing key for the optional agent-media.ai SSO integration. Unset = GET /user/agent-media-sso returns { url: null }

Security ​

VariableDefaultPurpose
ENCRYPTION_KEY(derived from JWT_SECRET)32-byte base64 or hex key for AES-256-GCM encryption at rest. Falls back to SHA-256 of JWT_SECRET if unset. See Security. Warning: introducing ENCRYPTION_KEY on a deployment that previously derived the key from JWT_SECRET makes all existing v2:-prefixed secrets undecryptable (single-key model) — set it from the first deploy, or plan a re-encryption of stored secrets
INTEGRATION_RETURN_URL_ALLOWLIST—Comma-separated allowed partner origins for integration/enterprise return URLs
SSRF_ALLOWED_PRIVATE_CIDRS—Comma-separated private CIDRs to allow for self-hosted provider instances (opt-in SSRF exception)
RESTRICT_UPLOAD_DOMAINS—When set, media attached to a post must contain this domain in its path; saving a post with externally-hosted media is rejected (HTTP 400). Use the domain of your own upload endpoint
NOT_SECURED—Dev-only toggle. With NODE_ENV=development it skips Helmet/HSTS and drops the Secure/sameSite flags on auth and CSRF cookies; it also suppresses the production fail-fast config exit. Never set in production

Payments ​

Payment providers are enabled by their keys; billing is on when at least one is set. Details and per-provider setup: Subscriptions & payment providers.

VariableDefaultPurpose
PAYMENTS_PROVIDER—Which enabled provider serves web checkout when several are enabled (stripe, …). Unset with several enabled → error logged, Stripe preferred
STRIPE_PUBLISHABLE_KEY—Stripe publishable key — enables the Stripe provider
STRIPE_SECRET_KEY—Stripe secret key
STRIPE_SIGNING_KEY—Stripe webhook signing secret
STRIPE_DISCOUNT_ID—Stripe coupon ID. When set, eligible existing paying customers on a monthly plan (no yearly plan, no existing discount) can have the coupon applied to their subscription
PAYPAL_CLIENT_ID—PayPal REST app client id — enables the PayPal provider
PAYPAL_CLIENT_SECRET—PayPal REST app secret
PAYPAL_WEBHOOK_ID—Id of the registered PayPal webhook (signature verification)
PAYPAL_ENVlivelive or sandbox
PAYPAL_BRAND_NAMEPostmillName shown on PayPal's approval page
APPLE_IAP_BUNDLE_ID—Mobile app bundle id — enables the App Store provider
APPLE_IAP_ISSUER_ID / APPLE_IAP_KEY_ID / APPLE_IAP_PRIVATE_KEY—App Store Connect In-App Purchase key (base64 .p8)
APPLE_IAP_APP_APPLE_ID—Numeric Apple ID of the app
APPLE_IAP_ENV / APPLE_IAP_ALLOW_SANDBOXProduction / —Store environment; true also accepts sandbox purchases
APPLE_IAP_SANDBOX_ORG_IDS—Comma-separated organization ids allowed to activate sandbox purchases (see Subscriptions)
PAYMENTS_APPLE_PRODUCT_PREFIXpostmillApp Store product-id prefix
GOOGLE_PLAY_PACKAGE_NAME—Mobile app package name — enables the Google Play provider
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON—Base64 service-account key with Play Developer API access
GOOGLE_PLAY_RTDN_SERVICE_ACCOUNT_EMAIL / GOOGLE_PLAY_RTDN_AUDIENCE—Pub/Sub push authentication (service account email; audience defaults to the webhook URL)
PAYMENTS_GOOGLE_PRODUCT_PREFIXpostmillGoogle Play product-id prefix
ADDON_STORAGE_GB_PER_PACK25Gigabytes added by one storage add-on pack
ADDON_VIDEO_EXPORTS_PER_PACK50Video exports added by one video-exports add-on pack
ADDON_CHANNELS_PER_PACK5Channels added by one channels add-on pack
ADDON_TEAM_SEATS_PER_PACK5Team seats added by one team-seats add-on pack
ADDON_POSTS_PER_PACK500Posts per month added by one posts add-on pack
ADDON_BRAND_KITS_PER_PACK5Brand kits added by one brand-kits add-on pack
ADDON_WEBHOOKS_PER_PACK10Webhooks added by one webhooks add-on pack
ADDON_COMPETITORS_PER_PACK10Competitors added by one competitors add-on pack
ADDON_STORAGE_PRICE_CENTS1900Price per storage add-on pack, in USD cents per month
ADDON_VIDEO_EXPORTS_PRICE_CENTS1900Price per video-exports add-on pack, in USD cents per month
ADDON_CHANNELS_PRICE_CENTS1900Price per channels add-on pack, in USD cents per month
ADDON_TEAM_SEATS_PRICE_CENTS1500Price per team-seats add-on pack, in USD cents per month
ADDON_POSTS_PRICE_CENTS900Price per posts add-on pack, in USD cents per month
ADDON_BRAND_KITS_PRICE_CENTS900Price per brand-kits add-on pack, in USD cents per month
ADDON_WEBHOOKS_PRICE_CENTS900Price per webhooks add-on pack, in USD cents per month
ADDON_COMPETITORS_PRICE_CENTS900Price per competitors add-on pack, in USD cents per month
NEXT_PUBLIC_ADDON_STORAGE_GB_PER_PACK25Browser-visible mirror of ADDON_STORAGE_GB_PER_PACK
NEXT_PUBLIC_ADDON_VIDEO_EXPORTS_PER_PACK50Browser-visible mirror of ADDON_VIDEO_EXPORTS_PER_PACK
NEXT_PUBLIC_ADDON_CHANNELS_PER_PACK5Browser-visible mirror of ADDON_CHANNELS_PER_PACK
NEXT_PUBLIC_ADDON_TEAM_SEATS_PER_PACK5Browser-visible mirror of ADDON_TEAM_SEATS_PER_PACK
NEXT_PUBLIC_ADDON_POSTS_PER_PACK500Browser-visible mirror of ADDON_POSTS_PER_PACK
NEXT_PUBLIC_ADDON_BRAND_KITS_PER_PACK5Browser-visible mirror of ADDON_BRAND_KITS_PER_PACK
NEXT_PUBLIC_ADDON_WEBHOOKS_PER_PACK10Browser-visible mirror of ADDON_WEBHOOKS_PER_PACK
NEXT_PUBLIC_ADDON_COMPETITORS_PER_PACK10Browser-visible mirror of ADDON_COMPETITORS_PER_PACK
NEXT_PUBLIC_ADDON_STORAGE_PRICE_CENTS1900Browser-visible mirror of ADDON_STORAGE_PRICE_CENTS
NEXT_PUBLIC_ADDON_VIDEO_EXPORTS_PRICE_CENTS1900Browser-visible mirror of ADDON_VIDEO_EXPORTS_PRICE_CENTS
NEXT_PUBLIC_ADDON_CHANNELS_PRICE_CENTS1900Browser-visible mirror of ADDON_CHANNELS_PRICE_CENTS
NEXT_PUBLIC_ADDON_TEAM_SEATS_PRICE_CENTS1500Browser-visible mirror of ADDON_TEAM_SEATS_PRICE_CENTS
NEXT_PUBLIC_ADDON_POSTS_PRICE_CENTS900Browser-visible mirror of ADDON_POSTS_PRICE_CENTS
NEXT_PUBLIC_ADDON_BRAND_KITS_PRICE_CENTS900Browser-visible mirror of ADDON_BRAND_KITS_PRICE_CENTS
NEXT_PUBLIC_ADDON_WEBHOOKS_PRICE_CENTS900Browser-visible mirror of ADDON_WEBHOOKS_PRICE_CENTS
NEXT_PUBLIC_ADDON_COMPETITORS_PRICE_CENTS900Browser-visible mirror of ADDON_COMPETITORS_PRICE_CENTS

Build-time/runtime desync warning: the NEXT_PUBLIC_ADDON_* values are baked into the frontend bundle at build time and duplicate the backend defaults. If you change a backend ADDON_* variable, you must rebuild the frontend with matching NEXT_PUBLIC_ADDON_* values — otherwise the UI shows stale pack sizes and prices while the backend enforces the new ones. Changing an ADDON_*_PRICE_CENTS variable creates a new Stripe Price for new purchases only; existing add-on subscriptions keep billing the old price (see Subscriptions & Stripe).

Plan and add-on prices are created dynamically from pricing.ts; no STRIPE_PRICE_* IDs are read from the environment. See Subscriptions & Stripe.

SSO / OIDC login ​

Login providers are managed by the separate administration app (a distinct repository) and stored encrypted in AuthProviderConfig. This repo reads that config DB-first and ships no /admin frontend or login-provider write API. The variables below remain supported as the bootstrap fallback when no enabled DB config exists for that provider. Email/password (LOCAL) login is always available regardless of provider config.

See OAuth / SSO for a complete setup walkthrough.

VariableDefaultPurpose
POSTMILL_GENERIC_OAUTHfalseSet to true to enable generic OIDC login
POSTMILL_OAUTH_AUTH_URL—OIDC provider authorization endpoint
POSTMILL_OAUTH_TOKEN_URL—OIDC provider token endpoint
POSTMILL_OAUTH_USERINFO_URL—OIDC provider userinfo endpoint
POSTMILL_OAUTH_CLIENT_ID—OIDC client ID
POSTMILL_OAUTH_CLIENT_SECRET—OIDC client secret
POSTMILL_OAUTH_SCOPEopenid profile emailOIDC scopes to request
NEXT_PUBLIC_POSTMILL_OAUTH_DISPLAY_NAME—Name shown on the login button
NEXT_PUBLIC_POSTMILL_OAUTH_LOGO_URL—Logo URL shown on the login button

Postmill ID federation ​

"Sign in with Postmill" lets a pinned first-party relying party (the template store) authenticate users of this instance. There is no client registration or secret, and the signing key is generated on first use — nothing needs configuring unless you change the defaults below. FRONTEND_URL must be set: it is the published authorization_endpoint.

VariableDefaultPurpose
FEDERATION_TRUSTED_REDIRECT_URIShttps://templates.postmill.ai/auth/callback/postmillComma-separated allow-list of redirect URIs an authorization code may be sent to. Exact full-string match (a trailing slash counts). Setting it replaces the default, so re-list production when adding a dev URL
FEDERATION_ISSUER(backend URL)Explicit iss for issued id_tokens. Falls back to NEXT_PUBLIC_OVERRIDE_BACKEND_URL, then BACKEND_URL, then NEXT_PUBLIC_BACKEND_URL

Social login bootstrap ​

These env vars are used only when no enabled DB config exists for the provider.

VariableDefaultPurpose
GITHUB_CLIENT_ID—GitHub OAuth app client ID
GITHUB_CLIENT_SECRET—GitHub OAuth app client secret
YOUTUBE_CLIENT_ID—Google OAuth login client ID. Also the platform channel app for YouTube posting (see below); Settings → Channels remains the per-org BYO override
YOUTUBE_CLIENT_SECRET—Google OAuth login client secret (dual-use, same as above)
NEYNAR_SECRET_KEY—Farcaster (Neynar) login only

Channel OAuth apps (platform click-connect) ​

Setting a provider's platform OAuth app credentials here gives every organization one-click Connect without per-org key entry. Leaving a provider unset requires each org to add its own app via Settings → Channels. A per-org config always takes precedence. Step-by-step portal setup for each provider: Platform Channel Apps.

Apart from the operator-level payment providers above, these channel and comms variables are the only provider credentials read from the environment. AI provider keys, short-link, and storage credentials are configured per-org in-app and have no env fallback; login-provider variables must never be used as AI credentials.

VariableProvider
X_API_KEY / X_API_SECRETX
LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRETLinkedIn
LINKEDIN_PAGE_CLIENT_ID / LINKEDIN_PAGE_CLIENT_SECRETLinkedIn Page (needs a dedicated LinkedIn app — Community Management API must be its only product)
FACEBOOK_APP_ID / FACEBOOK_APP_SECRETFacebook (also Instagram via Facebook login)
FACEBOOK_CONFIG_IDFacebook Login for Business Configuration ID (optional; required for FBfB-only Meta apps)
INSTAGRAM_APP_ID / INSTAGRAM_APP_SECRETInstagram standalone
DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRET / DISCORD_BOT_TOKENDiscord (the bot token is also the comms platform app's)
SLACK_ID / SLACK_SECRETSlack
TIKTOK_CLIENT_ID / TIKTOK_CLIENT_SECRETTikTok
YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRETYouTube channel posting (shared with Google login)
PINTEREST_CLIENT_ID / PINTEREST_CLIENT_SECRETPinterest
REDDIT_CLIENT_ID / REDDIT_CLIENT_SECRETReddit
TWITCH_CLIENT_ID / TWITCH_CLIENT_SECRETTwitch
THREADS_APP_ID / THREADS_APP_SECRETThreads
DRIBBBLE_CLIENT_ID / DRIBBBLE_CLIENT_SECRETDribbble
MASTODON_CLIENT_ID / MASTODON_CLIENT_SECRETMastodon (optional — tenants can also just enter their instance hostname)
MEWE_APP_ID / MEWE_API_KEYMewe
KICK_CLIENT_ID / KICK_SECRETKick
GOOGLE_GMB_CLIENT_ID / GOOGLE_GMB_CLIENT_SECRETGoogle Business Profile
NEYNAR_CLIENT_ID / NEYNAR_SECRET_KEYFarcaster (Wrapcast) channel posting
VK_IDVK (id only, no secret)
WHOP_CLIENT_IDWhop (id only, PKCE)
TELEGRAM_TOKENTelegram bot token (token-only)
LINE_CHANNEL_ACCESS_TOKENLINE Messaging API channel access token (token-only)
SLACK_SIGNING_SECRETSlack comms platform app — webhook signing secret (comms only; see Platform Comms Apps)
DISCORD_PUBLIC_KEYDiscord comms platform app — interaction webhook verification (comms only; see Platform Comms Apps)
LINE_CHANNEL_SECRETLINE comms platform app — webhook signature verification (comms only; see Platform Comms Apps)

Three opt-in flags add the platform channel apps as login providers (the login page never advertises a provider whose credentials are incomplete). Facebook and LinkedIn reuse the channel creds above; X login needs its own OAuth 2.0 client pair — the OAuth 1.0a X_API_KEY / X_API_SECRET are channel-only. See Platform Channel Apps → SSO dual-use. Sign in with Apple is a separate, login-only provider with its own credentials (last rows below).

VariableDefaultPurpose
FACEBOOK_SSO_ENABLEDfalseSet to true to add a "Continue with Facebook" login button (requires FACEBOOK_APP_ID / FACEBOOK_APP_SECRET)
FACEBOOK_SSO_CONFIG_ID—Facebook Login for Business Configuration ID used for login on Business-type Meta apps (login-only Configuration, "User access token" variation). Unset = falls back to the channel FACEBOOK_CONFIG_ID; with neither, the classic scope=public_profile,email dialog is used (Consumer-type apps)
X_SSO_ENABLEDfalseSet to true to add a "Continue with X" login button (requires X_CLIENT_ID / X_CLIENT_SECRET)
X_CLIENT_ID / X_CLIENT_SECRET—The X app's OAuth 2.0 Client ID and Client Secret (Keys and tokens; issued once OAuth 2.0 is enabled under User authentication settings). Login only — never used for posting. Login requests tweet.read users.read users.email; enable "Request email from users" on the app for the real address
LINKEDIN_SSO_ENABLEDfalseSet to true to add a "Continue with LinkedIn" login button (requires LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET)
APPLE_SSO_ENABLEDfalseSet to true to add Sign in with Apple (login only — no channel app). Requires the four APPLE_* values below; see OAuth / SSO → Apple login
APPLE_CLIENT_ID / APPLE_TEAM_ID / APPLE_KEY_ID / APPLE_PRIVATE_KEY—Apple Services ID, Developer Team ID, Sign in with Apple key ID, and the base64-encoded .p8 key

X channel behaviour ​

VariableDefaultPurpose
DISABLE_X_ANALYTICS—Set to skip X (Twitter) analytics collection
X_ANALYTICS_MAX_PAGE_DEPTH10Maximum timeline pages fetched per X analytics sync
STRIP_LINKS_FROM_X_POSTS—Set to strip links from X posts before publishing

Browser extension ​

VariableDefaultPurpose
EXTENSION_ID—Chrome extension ID for cookie-based platform integrations (Skool)

Monitoring ​

VariableDefaultPurpose
NEXT_PUBLIC_SENTRY_DSN—Sentry DSN for error tracking (frontend)
SENTRY_SPOTLIGHT—Set to 1 to enable Spotlight debug proxy
DATAFAST_API_KEY—Datafast API key. Posts a register goal to datafa.st when a new user signs up carrying a datafast_visitor_id
FACEBOOK_PIXEL_ACCESS_TOKEN—Meta Conversions API access token for server-side pixel events
NEXT_PUBLIC_FACEBOOK_PIXEL—Meta pixel ID (browser-visible). Server-side pixel events fire only when both this and FACEBOOK_PIXEL_ACCESS_TOKEN are set
NEXT_PUBLIC_PLAUSIBLE_DOMAIN—Plausible-registered domain for the frontend analytics tracker. Unset = tracker off (legacy fallback: postmill.ai when billing is enabled)

Provider errors vs. Postmill errors. When an organization's own AI / media provider (OpenAI, Gemini, Runway, …) rejects a request — bad key, quota or billing limit, rate limit, invalid request, outage — the API answers 502 with { error: "ProviderUpstreamError", provider, providerName, kind, upstreamStatus, message, settingsUrl } and the UI shows the provider's name and message ("Google AI Studio reports the account's quota or billing limit was reached (HTTP 429): …"). kind is one of auth, quota, rate_limit, invalid_request, timeout, unavailable, unknown. The backend logs one provider upstream error <domain>/<provider> <kind> HTTP <status> warning per occurrence. Sentry receives onlyunavailable / timeout / unknown kinds, as warnings tagged provider + kind (provider outages are worth seeing; a user's expired key or exhausted plan is not an application error). A 500 from the API therefore always means a Postmill-side failure.

AI Designer chatbot ​

VariableDefaultPurpose
AI_DESIGNER_MESH_STOREredisAgent-mesh session/breaker store: redis (default) or postgres (opt-in, runs its own DDL)
AI_DESIGNER_MESH_DATABASE_URL—Dedicated Postgres URL for the opt-in mesh Postgres store. Never the Prisma DATABASE_URL
AI_DESIGNER_MESH_CONNECTION_LIMIT10Connection pool size for the opt-in mesh Postgres store
AI_DESIGNER_AGENT_REGISTRY—Directory of per-agent YAML files overriding the bundled registry
AI_DESIGNER_AGENT_TIMEOUT_MS120000Per-agent LLM dispatch deadline
AI_DESIGNER_ASSET_TIMEOUT_MS90000Asset (image generation/stock) step deadline
AI_DESIGNER_SCENE_VIDEO_TIMEOUT_MS480000Deadline for one generated scene video in a video session
AI_DESIGNER_STUCK_SESSION_MINUTES15Planning/executing sessions untouched longer than this roll back to awaiting_plan
TRUST_PROXY_HOPS(disabled)Number of XFF-appending reverse proxies in front of the backend. Unset/invalid = key on the socket peer address (safe default, not spoofable). When set, per-IP rate buckets key on the Nth-from-right x-forwarded-for entry. Applies to the /ai-designer connect-rate bucket and the HTTP throttler's per-IP buckets (login/register/enterprise/public-report) and the MCP rate limits. Operators behind a proxy must set it to the exact number of appending proxies — overestimating lands in attacker-controlled left-most XFF entries and makes the limits spoofable
SESSION_TTL_MINUTES—agent-mesh package env var — no Postmill effect (see caveat below)
ENABLE_CIRCUIT_BREAKER—agent-mesh package env var — forced off (see caveat below)
MCP_MAX_RETRIES—agent-mesh package env var — no Postmill effect (see caveat below)

SESSION_TTL_MINUTES, ENABLE_CIRCUIT_BREAKER, and MCP_MAX_RETRIES belong to the bundled @reaatech/agent-mesh package's env schema, not to Postmill itself. agent-mesh-env.stash.ts reads, neutralizes, and restores them around the package's import-time env parse: out-of-range values are stashed aside so they cannot crash the boot, and ENABLE_CIRCUIT_BREAKER is forced off because the package's global breaker is keyed by agent id only — Postmill uses the conductor's per-(org, agent) breaker instead. Setting these vars does not configure Postmill behaviour.

Local development feature flags ​

Set any of these to true or 1 to disable the corresponding subsystem during local development. All features remain enabled by default.

VariablePurpose
DEV_DISABLE_AISkip AI adapter registration
DEV_DISABLE_MCPSkip Mastra/MCP/A2A server startup
DEV_DISABLE_MEDIASkip media-generation adapter registration
DEV_DISABLE_SHORTLINKSSkip short-link adapter registration
DEV_DISABLE_EMAILSkip email-provider adapter registration
DEV_DISABLE_VIDEOSkip video-generation adapter registration
DEV_DISABLE_AGENTSkip agent-graph services
DEV_DISABLE_CRONSkip ScheduleModule.forRoot()
DEV_DISABLE_SENTRYSkip Sentry initialization
DEV_DISABLE_OPENTELEMETRYSkip OpenTelemetry exporter setup
DEV_DISABLE_COMMSSkip comms (chat-app) adapter registration
AGENT_SUPERVISOR_ENABLEDtrue (default) uses the supervisor + specialists agent model
CONTENT_PIPELINE_TOTAL_TIMEOUT_MS300000
CONTENT_PIPELINE_AGENT_TIMEOUT_MS120000
BACKEND_URLServer-side backend URL used by the MCP surface. Falls back to NEXT_PUBLIC_BACKEND_URL
MEDIA_MCP_AUDIT_LOG_PATH/tmp/media-mcp-audit.log
SENTRY_PROFILINGSet to 1 to enable browser profiling in dev
FRONTEND_PROFILINGSet to 1 to enable Document-Policy: js-profiling in dev
DEV_SEED_DEMOPopulate the target org with placeholder demo data at backend boot (dev only)
DEV_SEED_DEMO_RESETWipe and reseed demo data (dev only)
DEV_SEED_DEMO_EMAILDemo seeder account email
DEV_SEED_DEMO_PASSWORDDemo seeder account password

Production, scaling & observability ​

VariableDefaultPurpose
CONFIG_CHECK_STRICT—Fail fast on fatal-missing secrets even in dev
BACKEND_LISTEN_HOST(Node default)Explicit bind host for the backend HTTP server (e.g. 0.0.0.0 to force IPv4 in CI/containers). Unset = Node's default bind address
COLLAB_SINGLE_INSTANCEtrueCollaboration websocket keeps Yjs state in memory; must be true unless COLLAB_REDIS_ADAPTER is set
COLLAB_REDIS_ADAPTER—Reserved for the future Yjs-over-Redis adapter
OUTBOUND_HTTP_TIMEOUT_MS30000Bound provider and webhook calls
WEBHOOK_TIMEOUT_MS10000Outbound webhook delivery timeout
WEBHOOK_SIGNING_SECRET—HMAC secret for X-Postmill-Signature. When unset, derives from JWT_SECRET
OTEL_EXPORTER_OTLP_ENDPOINT—OTLP/HTTP tracing endpoint. Off unless set
OTEL_SERVICE_NAMEpostmill-backendOpenTelemetry service name
DATABASE_CONNECTION_LIMIT—Prisma connection pool size appended to DATABASE_URL
DATABASE_POOL_TIMEOUT—Prisma pool timeout appended to DATABASE_URL
ALLOW_DESTRUCTIVE_SCHEMAfalseAllow prisma db push to perform destructive diffs
ERRORS_RETENTION_DAYS90Retention for Errors rows
NOTIFICATIONS_RETENTION_DAYS180Retention for Notifications (+ NotificationRead)
MULTIPART_UPLOAD_RETENTION_DAYS7Retention for abandoned multipart uploads
MASTRA_TRACE_RETENTION_DAYS30Retention for Mastra traces/scorers
SOFT_DELETE_RETENTION_DAYS30Hard-purge window for soft-deleted posts/files
BLANK_DESIGN_RETENTION_DAYS1Days an untouched, content-free Untitled Design is kept before the retention sweep soft-deletes it (with its preview file)
IP_RETENTION_DAYS90Null User/Session IP and agent after this window
AI_DESIGNER_SESSION_RETENTION_DAYS90Retention for AI Designer chat sessions
DESIGN_VERSIONS_KEEP_AUTO10Automatic version snapshots kept per Designer design (oldest trimmed on insert)
DESIGN_VERSIONS_KEEP_NAMED20Named version snapshots kept per Designer design (oldest trimmed on insert)

Stock media ​

VariableDefaultPurpose
UNSPLASH_ACCESS_KEY—Unsplash API access key for stock photos
PEXELS_API_KEY—Pexels API key for stock videos
PIXABAY_API_KEY—Pixabay API key for vectors/illustrations
GIPHY_API_KEY—GIPHY API key for stickers
JAMENDO_CLIENT_ID—Jamendo API client ID for stock audio

Iconify (SVG icons) does not require an API key.

Content Packs ​

Premium stock sources are configured per-organization in-app via Settings → Content Packs. BYOK packs take precedence over the free catalogs above for the capabilities they support. See Storage and the user-facing settings docs.

Video rendering ​

See Video Rendering for the full list of VIDEO_RENDER_* variables.

Headless Chromium (Puppeteer) ​

VariableDefaultPurpose
PUPPETEER_EXECUTABLE_PATH(bundled Chromium)Path to a system/distro Chromium binary for design frame capture (used by the render-worker container). Unset = Puppeteer's bundled Chromium
PUPPETEER_DISABLE_SANDBOXfalseSet to true to launch the campaign-report PDF Chromium with --no-sandbox (for CI/containers without user namespaces; also disabled automatically when CI=true). Production defaults to sandboxed mode

Verified against v1.3.0

The AI-native social media management platform — postmill.ai