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
| Variable | Default | Required | Prod | What it does |
|---|---|---|---|---|
CRON_SECRET | — | Yes | ✅ | Bearer secret for the cron, relay, and approval-signing. Whoever holds it can publish — treat as a publishing credential. |
SOCIAL_PUBLIC_URL | request origin | No | ❌ | Base URL used to build approval links. Falls back to the incoming request's origin, which is usually right. |
Drafting
| Variable | Default | Required | Prod | What it does |
|---|---|---|---|---|
SOCIAL_AUTOPOST_PRODUCTS | empty | For the cron | ❌ | Comma-separated brands opted into auto-drafting. Empty means the cron does nothing — this is why it is currently a no-op. |
SOCIAL_DRAFT_SOURCE | hermes | No | ❌ | hermes 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_FALLBACK | unset | No | ❌ | Set 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_MODEL | claude-opus-4-8 | No | ❌ | Model used when the source is anthropic (or the fallback fires). |
SOCIAL_DRAFT_LOCALE | ar | No | ❌ | ar or en. Arabic-first is the house default. |
ANTHROPIC_API_KEY | — | If source is anthropic | ✅ | Server-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.
| Variable | Default | Required | Prod | What it does |
|---|---|---|---|---|
SOCIAL_REVIEW_CHANNEL | slack | No | ❌ | Which channel Hermes relays drafts into. |
TELEGRAM_REVIEW_CHAT_ID | — | Fallback | ❌ | A private chat id. Must never be the public brand channel — drafts carry one-click publish links. |
Telegram
| Variable | Default | Required | Prod | What it does |
|---|---|---|---|---|
TELEGRAM_BOT_TOKEN | — | Yes | ❌ | From BotFather. Present locally, never pushed to production. |
TELEGRAM_CHANNEL_ID | — | Yes | ❌ | Public brand channel, @handle or -100…. Does not exist anywhere yet. |
Per brand, with the product id uppercased (HOGWARTS, MKAN, DATABAYT, …):
| Variable | Required | Prod | What it does |
|---|---|---|---|
FACEBOOK_PAGE_ID_<PRODUCT> | Yes | ✅ ×3 | The Page to post to |
FACEBOOK_PAGE_ACCESS_TOKEN_<PRODUCT> | Yes | ✅ ×3 | Permanent Page token (expires_at: 0) |
FACEBOOK_PAGE_ID | Legacy | ✅ | Unsuffixed fallback for one default brand only |
FACEBOOK_PAGE_ACCESS_TOKEN | Legacy | ✅ | " |
Configured for hogwarts, mkan, databayt. Not for sijillee or moalimee — no Page exists.
Hermes gateway
| Variable | Default | Required | Prod | What it does |
|---|---|---|---|---|
HERMES_API_URL | — | For the Hermes lane | ❌ | Gateway endpoint. Only meaningful where Hermes is reachable — i.e. local dev, never a cloud deployment. |
HERMES_API_KEY | — | No | ❌ | Optional bearer for that direction. |
NEXT_PUBLIC_HERMES_API_URL | — | No | ❌ | Display-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:
| Channel | Variables |
|---|---|
INSTAGRAM_ACCOUNT_ID_<PRODUCT> (reuses the Facebook Page token) | |
LINKEDIN_ORG_URN_<PRODUCT>, LINKEDIN_ACCESS_TOKEN_<PRODUCT>, LINKEDIN_REFRESH_TOKEN_<PRODUCT>, LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET | |
| X | X_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 aboveThen 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