setup-guides

Bluesky autopost — setup

The cafe-contest workflow can post to Bluesky on two events:

  1. 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.
  2. 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 varEffect when set
BSKY_HANDLEAccount 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_PASSWORDApp 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=trueFlips 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

  1. Create or pick an accountwritecafe.bsky.social recommended 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.
  2. 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).
  3. Set the env vars in Railway (or your backend host):
    • BSKY_HANDLE=writecafe.bsky.social
    • BSKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
    • Leave BLUESKY_AUTOPOST_ENABLED UNSET for the first week.
  4. Restart the backend (Railway redeploys on env var change).
  5. 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 at https://bsky.app/profile/<your-handle>.
  6. Once the dry run looks right, set BLUESKY_AUTOPOST_ENABLED=true and restart again. Going forward, every Monday submission_open transition + every Sunday closed transition 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.
  • uploadBlob returns 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.

BLUESKY AUTOPOST — Docs | HiveJournal