Bluesky autopost — setup
The cafe-contest workflow can post to Bluesky on two events:
- Monday-bento post — fires when a contest enters
submission_open, with the song + ingredients + prize + per-week URL and up to two of the DALL-E bento images embedded. - Winner reveal post — fires per category when a contest enters
closed, with the writer name, votes, prize, share URL, and the 1200×630 cafe-winner OG card embedded.
The integration speaks AT Protocol XRPC directly (no extra dep). It is feature-flagged on env vars; nothing posts unless the gates open.
Three gates
| Env var | Effect when set |
|---|---|
BSKY_HANDLE | Account handle, e.g. writecafe.bsky.social. With this + BSKY_APP_PASSWORD, admin "🦋 Post" buttons appear in /dashboard/admin/cafe/contests and the /api/cafe/contests/.../post-*-to-bluesky routes are usable. |
BSKY_APP_PASSWORD | App password generated at https://bsky.app/settings/app-passwords. Do NOT use the account's main password — app passwords scope what the integration can do and can be revoked individually. |
BLUESKY_AUTOPOST_ENABLED=true | Flips on auto-fire from transitionStatus. With this off but the two creds set, the integration only posts when an admin clicks a button (useful for dry-running before turning autopost on for real Mondays). |
Setup steps
- Create or pick an account —
writecafe.bsky.socialrecommended for clarity. If you already use a personal Bluesky, you may want a separate handle for the cafe so a follower of the cafe doesn't get personal posts. - Generate an app password — Bluesky web app → Settings → App Passwords → "Add App Password". Name it something memorable like
hivejournal-cafe-autopost. Copy the 4-word password immediately (it's shown once). - Set the env vars in Railway (or your backend host):
BSKY_HANDLE=writecafe.bsky.socialBSKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx- Leave
BLUESKY_AUTOPOST_ENABLEDUNSET for the first week.
- Restart the backend (Railway redeploys on env var change).
- Smoke-test from the admin dashboard — go to
/dashboard/admin/cafe/contests. With creds set, "🦋 Post bento to Bluesky" appears on every non-draft contest card. Click it on a closed contest first (lower stakes than a live one). Confirm the post appears athttps://bsky.app/profile/<your-handle>. - Once the dry run looks right, set
BLUESKY_AUTOPOST_ENABLED=trueand restart again. Going forward, every Mondaysubmission_opentransition + every Sundayclosedtransition will post automatically.
Idempotency
The post URI is stamped on the row that produced it (cafe_contests.bluesky_post_uri for Monday bentos, cafe_contest_winners.bluesky_post_uri for winners). Repeat triggers no-op unless force: true is passed in the body — that's why the admin button confirms before re-firing on an already-posted contest.
Common failures
- Login fails — wrong handle or app password, or app password has been revoked. Generate a new one.
uploadBlobreturns nothing in logs — image is over Bluesky's 1MB blob cap. The post still goes out without that image; the others embed normally. The cafe-bento images we generate are usually <1MB, but if you swap in larger PNGs upstream, they'll skip silently.- Post text gets truncated — Bluesky's hard cap is 300 chars; the service caps at 280 to leave headroom for trailing whitespace and ellipses. Long song titles or character names eat into the budget; the bento line gets truncated first.
What gets posted
Both post types include #writingcommunity #shortstory as facet-encoded hashtags so they show up in those Bluesky topic feeds. Edit those tags in cafe-bluesky.ts if you'd rather route to different communities.
URLs are also facet-encoded (UTF-8 byte offsets, per AT Protocol spec) so they render clickable on bsky.app.