Automated Bluesky campaign scheduling β AT Protocol + Cloudflare D1 + thread chaining
A production-grade campaign scheduling engine for Bluesky. Schedule multi-post thread campaigns with images, facets (links/mentions/tags), and precise timing β all running serverless on Cloudflare's edge.
Built and battle-tested running real investigative journalism campaigns on Bluesky.
- π§΅ Thread Chaining β proper reply chains with root/parent URI tracking
- πΌοΈ Image Embedding β automatic image upload + blob attachment
- π·οΈ Facet Support β links, mentions, hashtags auto-detected and encoded
- β±οΈ D1-Based Scheduling β fire at precise times via CF Cron
- π Fire-Once Lock β D1 state prevents double-posting
- π¦ Campaign Archive β completed campaigns archived, never deleted
- π REST API β inject campaigns via
/inject, check status via/status
POST /inject (campaign payload)
β
βΌ
schedule-worker (D1: schedule-db)
β
CF Cron Trigger (every minute check)
β
βΌ
bsky-worker βββΊ AT Protocol API βββΊ Bluesky
β
βββ Update D1 (post URIs, CIDs, status)
{
"campaign_id": "my-campaign-001",
"handle": "yourhandle.bsky.social",
"posts": [
{
"post_index": 0,
"text": "π§΅ THREAD: My investigation into X...",
"image_url": "https://example.com/image.jpg",
"scheduled_at": "2026-05-02T14:00:00Z"
},
{
"post_index": 1,
"text": "1/ Here's what we found...",
"scheduled_at": "2026-05-02T14:05:00Z"
}
]
}git clone https://github.com/indicaindependent/bsky-campaign-engine
cd bsky-campaign-engine
cp wrangler.toml.example wrangler.toml
wrangler d1 create campaign-db
wrangler secret put BSKY_APP_PASS
wrangler secret put WORKER_SECRET
wrangler deployAll posts are validated against Bluesky's 300-character limit (JS length, not byte count). Flag emoji = 4 chars. Hard ceiling enforced before posting.