Queue with a pull consumer
Do the Cloudflare setup first.
Devices post readings to the Worker, which puts each request on a Cloudflare Queue. The queue has an HTTP pull consumer instead of a consumer Worker: every minute the writer pulls up to 1,000 messages over the Cloudflare API (100 per request), appends them as one commit, and acknowledges them. Messages it doesn't acknowledge within the 2-minute lease come back. After 5 failed deliveries a message moves to a dead-letter queue.
npx wrangler queues create timelake-readings-dlq
npx wrangler queues create timelake-readings
npx wrangler queues consumer http add timelake-readings \
--batch-size 100 --message-retries 5 --visibility-timeout-secs 120 --dead-letter-queue timelake-readings-dlq
npx wrangler queues info timelake-readings # note the queue IDCreate an API token with Account → Queues → Edit (pulling and acknowledging need write access), and add it to timelake.env:
cat >> timelake.env <<'EOF'
export CF_ACCOUNT_ID="<account id>" CF_QUEUES_TOKEN="<queues api token>" READINGS_QUEUE_ID="<queue id>"
EOFBind the queue to the Worker in wrangler.jsonc, then run npx wrangler deploy again:
{
"name": "timelake-intake",
"main": "src/index.js",
"compatibility_date": "2026-10-01",
"r2_buckets": [{ "binding": "LAKE", "bucket_name": "timelake" }],
"queues": { "producers": [{ "binding": "READINGS", "queue": "timelake-readings" }] }
}The cron line:
* * * * * cd $HOME/timelake && . ./timelake.env && flock -n queue.lock $HOME/.local/bin/uv run --with timelake --with boto3 writer.py queue >> writer.log 2>&1
Try it:
curl -X POST -H "authorization: Bearer <token>" "https://timelake-intake.<your subdomain>.workers.dev/readings" \
--data '[{"time": "2026-01-05T10:05:00Z", "device_id": "pump-1", "temp": 62.0, "humidity": 41},
{"time": "2026-01-05T10:05:00Z", "device_id": "pump-2", "temp": 59.4, "humidity": 43}]'A message holds up to 128 KB. For readings sooner than once a minute, run writer.py queue in a loop with a short sleep instead of from cron.