Fly Machines --schedule is fuzzy: key a Sume job by period
Fly's --schedule starts a Machine on an approximate hourly, daily, weekly or monthly cycle, not at a set time. Key the Sume submit by period, not by start time.

fly machine run --schedule daily does not fire at a fixed clock time, so build the Sume Idempotency-Key from the period (the UTC date for daily, the UTC hour for hourly), never from the moment the Machine started. A restart in the same period then replays the same job instead of creating a second paid one.
What the Fly docs say
From the Fly.io flyctl reference (read 2026-10-10).
| Option | Fly says | Design consequence |
|---|---|---|
| --schedule | Values hourly, daily, weekly, monthly, on a fuzzy cycle | The start minute drifts; key by period |
| Timing | Started when you run the command, then once per approximate hour, day, week or month | Do not assume exact spacing between runs |
| --restart | no, always or on-fail | With on-fail, a crash re-runs the script: same key must be safe |
| --rm | Machine is destroyed on stop; default restart policy becomes no | Keep the job id in your store, not on the Machine |
The Machine's script
Bake this into the image and pass SUME_API_KEY as a Fly secret. Request fields follow the Image 1.0 docs. A daily period key is shown.
import json, os, urllib.request
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
slot = now.strftime("%Y-%m-%d")
body = {
"prompt": "Soft daylight product shot on a linen cloth",
"quality": "low",
"aspect_ratio": "4:5",
"mode": "webhook",
"webhook_url": "https://example.com/hooks/sume",
}
req = urllib.request.Request(
"https://api.sume.com/v1/image-1.0/generate",
data=json.dumps(body).encode(),
headers={
"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "nightly-hero-" + slot,
},
)
with urllib.request.urlopen(req, timeout=20) as r:
print(r.status, r.read().decode())Retries and exits
If the call returns 429 or 503, sleep and retry with the same key. The Errors and credits page says not to retry unsafe submits without an Idempotency-Key. Exit with a non-zero status only for a real failure, so an on-fail restart does not loop on a validation error.
Why a webhook and a slot key together
Submit in webhook mode so the scheduled process can exit. Sume sends only terminal events (job.completed, job.failed, job.canceled) and retries up to 10 attempts, 30 seconds apart, with a 10 second timeout for each attempt, per the Webhooks page. Use job_id as the idempotency key on your receiver.
Delivery is an optimization, not the only recovery path. Keep GET /v1/jobs/:id/status polls available for events that never arrive. A repeat of the same key with the same body is an exact retry. A repeat with a different body returns 409 idempotency_conflict, as the Generation admission page lists.
Sources
Related posts
More in Developers
- A for await loop over Sume job status: one async generator, 3 runtimes
Wrap Sume status polling in an async generator and read it with for await. One fetch-only file ran unchanged on Node, Bun and Deno and honors the poll hint.
- Generate a Go client from the Sume OpenAPI with oapi-codegen
The full Sume spec trips oapi-codegen on a [number, null] type, but filtering to the operations you use works. Config, generated names and a short caller.
- GitHub Actions concurrency for a Sume render job: the cancel trap
cancel-in-progress stops your workflow, not the Sume job it submitted. Group by branch, cancel via POST /v1/jobs/{id}/cancel in a final step, and cap the run.
- Go 1.27.2 and 1.26.9 patch net/http: update your Sume webhook receiver
Go 1.27.2 and 1.26.9 (2026-10-08) list security fixes in net/http and crypto/tls. A stdlib receiver for Sume webhooks that verifies the signature, in two files.
Written by Sume