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.

4 min readSume
All posts

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).

fly machine run options, from docs.fly.io/machines/flyctl/fly-machine-run (read 2026-10-10)
OptionFly saysDesign consequence
--scheduleValues hourly, daily, weekly, monthly, on a fuzzy cycleThe start minute drifts; key by period
TimingStarted when you run the command, then once per approximate hour, day, week or monthDo not assume exact spacing between runs
--restartno, always or on-failWith on-fail, a crash re-runs the script: same key must be safe
--rmMachine is destroyed on stop; default restart policy becomes noKeep 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

All Developers posts

Written by Sume