Schedule posts to Bluesky with Cloudflare workers.
skyscheduler.work
cf
tool
bsky-tool
cloudflare
bluesky
schedule
bsky
service
social-media
cloudflare-workers
1"$schema" = "./node_modules/wrangler/config-schema.json"
2name = "skyscheduler"
3main = "src/index.tsx"
4compatibility_date = "2025-11-18"
5compatibility_flags = [ "nodejs_compat", "nodejs_compat_do_not_populate_process_env", "disable_ctx_exports", "disable_nodejs_http_server_modules" ]
6send_metrics = false
7minify = true
8upload_source_maps = true
9preview_urls = false
10
11assets = { binding = "ASSETS", run_worker_first = ["/robots.txt"], directory = "./assets/", not_found_handling = "single-page-application" }
12routes = [{ pattern = "skyscheduler.work", custom_domain = true }]
13
14# Redis for sessions
15[[kv_namespaces]]
16binding = "KV"
17id = "0ecd892014ac47eea38c72be94586e7b"
18
19# Invite key pool
20[[kv_namespaces]]
21binding = "INVITE_POOL"
22id = "45e1cff45cad45c28b22c4fbcd30db00"
23
24[[d1_databases]]
25binding = "DB"
26database_name = "skyposts"
27database_id = "5553e724-4dfb-4771-9c08-3584da5855d1"
28migrations_table = "migrations"
29migrations_dir = "migrations"
30
31# media storage
32[[r2_buckets]]
33binding = "R2"
34bucket_name = "skyembeds"
35
36# temporary storage to get CF Images working, this bucket should be WAF'd so no one but the worker can access it.
37[[r2_buckets]]
38binding = "R2RESIZE"
39bucket_name = "skyscheduler-resize"
40
41# rate limits to help combat abuse
42[[ratelimits]]
43name = "REPOST_LIMITER"
44namespace_id = "5471001"
45simple = { limit = 4, period = 60 }
46[[ratelimits]]
47name = "POST_LIMITER"
48namespace_id = "5471002"
49simple = { limit = 5, period = 10 }
50[[ratelimits]]
51name = "UPDATE_LIMITER"
52namespace_id = "5471003"
53simple = { limit = 7, period = 60 }
54[[ratelimits]]
55name = "ACCOUNT_LIMITER"
56namespace_id = "5471004"
57simple = { limit = 10, period = 60 }
58
59[triggers]
60# Schedule cron triggers at the start of every hour and ~5:30pm on sunday for big cleanups:
61crons = [ "0 * * * *", "30 17 * * sun" ]
62
63# flags required to run wrangler in linux
64[dev]
65ip = "127.0.0.1"
66
67[observability]
68enabled = true
69head_sampling_rate = 1
70[observability.traces]
71enabled = true
72head_sampling_rate = 0.5
73persist = true
74
75[[queues.producers]]
76queue = "skyscheduler-post-queue"
77binding = "POST_QUEUE"
78
79[[queues.producers]]
80queue = "skyscheduler-repost-queue"
81binding = "REPOST_QUEUE"
82
83[[queues.consumers]]
84queue = "skyscheduler-post-queue"
85max_batch_size = 3
86max_batch_timeout = 5
87max_retries = 3
88
89[[queues.consumers]]
90queue = "skyscheduler-repost-queue"
91max_batch_size = 15
92max_batch_timeout = 10
93max_retries = 3
94
95# used for resizing the thumbnails for link posts
96[images]
97binding = "IMAGES"
98
99[build]
100command = "npm run minify"
101
102[vars]
103# the domain and protocol that this application is hosted on. note this value is also used for CORS
104BETTER_AUTH_URL="https://skyscheduler.work"
105
106# If we should use cf image transforms & how much of a quality percentage should we try and the public location of the resize bucket.
107IMAGE_SETTINGS={enabled=true, steps=[95, 85, 75], bucket_url="https://resize.skyscheduler.work/", max_width=3000}
108
109# Signup options and if keys should be used
110SIGNUP_SETTINGS = {use_captcha=true, invite_only=false, invite_thread="", invite_uses=10}
111
112# queue handling, pushing information.
113QUEUE_SETTINGS = {enabled=true, repostsEnabled=true, postNowEnabled=false, threadEnabled=true, pressure_retries=true, delay_val=100, max_retries=3, post_queues=["POST_QUEUE"], repost_queues=["REPOST_QUEUE"]}
114
115# redirect links
116REDIRECTS = {contact="https://bsky.app/profile/skyscheduler.work", tip="https://ko-fi.com/socksthewolf/tip"}
117
118# if we should be removing abandoned files from R2 storage
119R2_SETTINGS={auto_prune=true, prune_days=3}
120
121# settings about tasks
122TASK_SETTINGS={use_posts=true, use_reposts=true}
123
124# set this to true in your .dev.vars to turn off turnstile
125IN_DEV=false