Environment reference

Every environment variable the social pipeline reads — what sets it, its default, and whether it is currently configured in production.

Environment reference

Every variable the social pipeline actually reads, extracted from the source rather than from memory. Defaults are what the code falls back to when a variable is unset — several of them are load-bearing.

Production column reflects 2026-07-26 and matches Status.

Core

VariableDefaultRequiredProdWhat it does
CRON_SECRETYesBearer secret for the cron, relay, and approval-signing. Whoever holds it can publish — treat as a publishing credential.
SOCIAL_PUBLIC_URLrequest originNoBase URL used to build approval links. Falls back to the incoming request's origin, which is usually right.

Drafting

VariableDefaultRequiredProdWhat it does
SOCIAL_AUTOPOST_PRODUCTSemptyFor the cronComma-separated brands opted into auto-drafting. Empty means the cron does nothing — this is why it is currently a no-op.
SOCIAL_DRAFT_SOURCEhermesNohermes hands the request to the gateway; anthropic drafts server-side. The default dead-ends on a cloud deployment, since Hermes is not reachable from it.
SOCIAL_DRAFT_FALLBACKunsetNoSet to anthropic to draft server-side only when Hermes is down, instead of skipping the run. Opt-in for the same billing reason — an outage never silently spends.
SOCIAL_DRAFT_MODELclaude-opus-4-8NoModel used when the source is anthropic (or the fallback fires).
SOCIAL_DRAFT_LOCALEarNoar or en. Arabic-first is the house default.
ANTHROPIC_API_KEYIf source is anthropicServer-side drafting. Costs money per draft — an explicit spend decision, not a config tweak.

Review destination

The draft-and-approve loop needs somewhere private to send drafts. It tries Hermes/Slack first, then Telegram. If neither is configured, the cron fails after drafting.

VariableDefaultRequiredProdWhat it does
SOCIAL_REVIEW_CHANNELslackNoWhich channel Hermes relays drafts into.
TELEGRAM_REVIEW_CHAT_IDFallbackA private chat id. Must never be the public brand channel — drafts carry one-click publish links.

Telegram

VariableDefaultRequiredProdWhat it does
TELEGRAM_BOT_TOKENYesFrom BotFather. Present locally, never pushed to production.
TELEGRAM_CHANNEL_IDYesPublic brand channel, @handle or -100…. Does not exist anywhere yet.

Telegram setup →

Facebook

Per brand, with the product id uppercased (HOGWARTS, MKAN, DATABAYT, …):

VariableRequiredProdWhat it does
FACEBOOK_PAGE_ID_<PRODUCT>Yes✅ ×3The Page to post to
FACEBOOK_PAGE_ACCESS_TOKEN_<PRODUCT>Yes✅ ×3Permanent Page token (expires_at: 0)
FACEBOOK_PAGE_IDLegacyUnsuffixed fallback for one default brand only
FACEBOOK_PAGE_ACCESS_TOKENLegacy"

Configured for hogwarts, mkan, databayt. Not for sijillee or moalimee — no Page exists.

Facebook setup →

Hermes gateway

VariableDefaultRequiredProdWhat it does
HERMES_API_URLFor the Hermes laneGateway endpoint. Only meaningful where Hermes is reachable — i.e. local dev, never a cloud deployment.
HERMES_API_KEYNoOptional bearer for that direction.
NEXT_PUBLIC_HERMES_API_URLNoDisplay-only, shown in the status panel. The only browser-exposed value here — never put a token in a NEXT_PUBLIC_* variable.

Slack tokens (SLACK_BOT_TOKEN, SLACK_APP_TOKEN, SLACK_HOME_CHANNEL) belong on the Hermes machine, not in the web app. Slack setup →

Not yet wired

Named here so the shape is predictable when these channels land:

ChannelVariables
InstagramINSTAGRAM_ACCOUNT_ID_<PRODUCT> (reuses the Facebook Page token)
LinkedInLINKEDIN_ORG_URN_<PRODUCT>, LINKEDIN_ACCESS_TOKEN_<PRODUCT>, LINKEDIN_REFRESH_TOKEN_<PRODUCT>, LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET
XX_API_KEY, X_API_SECRET, X_ACCESS_TOKEN_<PRODUCT>, X_ACCESS_TOKEN_SECRET_<PRODUCT>

Rules

Server-side only. The single browser-exposed variable is NEXT_PUBLIC_HERMES_API_URL, which is a URL for a status display. Every token stays server-side. A NEXT_PUBLIC_ token is a public token.

Trim on read. Vercel environment variables can carry a trailing newline that survives copy-paste and breaks header and URL construction in ways that are painful to debug. Every read in this codebase calls .trim() — do the same.

One central .env locally. No .env.local, no .env.development.

Per-brand, never shared. One leaked token should compromise one brand, not all of them.

Quickest path from nothing to a working loop

Given a deployment that already has CRON_SECRET and Facebook configured, three variables bring the entire draft-and-approve loop alive:

TELEGRAM_BOT_TOKEN=...          # from BotFather
TELEGRAM_CHANNEL_ID=@yourbrand  # the public channel
TELEGRAM_REVIEW_CHAT_ID=-100... # a PRIVATE chat, not the above

Then opt a brand in and pick a reachable draft source:

SOCIAL_AUTOPOST_PRODUCTS=databayt
SOCIAL_DRAFT_SOURCE=anthropic   # spend decision — the default is unreachable from the cloud