Cloudflare Queue consumer 15 min wall time: poll a Sume job

A Queue consumer on Cloudflare can run 15 minutes of wall time, but a Sume job can wait longer. Re-enqueue with a delay and honor next_poll_after_seconds.

4 min readSume
All posts

Do not hold a Cloudflare Queue consumer open until a Sume job finishes. The consumer has a 15 minute wall time, the job can run longer, and a Worker pays for the wait. Read the job once, return, and put the job id back on the queue to run after the number of seconds that next_poll_after_seconds gives.

The Cloudflare limits

The Workers limits page (updated 2026-09-05) lists the values below. ctx.waitUntil() extends work for up to 30 seconds after the response, which is enough to submit a job but not to wait for one.

Cloudflare Workers limits, read 2026-10-08
LimitValue
Queue consumer wall time15 minutes
ctx.waitUntil() after responseUp to 30 seconds
CPU time, Free10 ms
CPU time, Paid30 s default, up to 5 min
Subrequests per invocation50 Free, 10,000 Paid

The re-enqueue loop

Each message holds a job_id. The consumer reads GET /v1/jobs/{id}, and if terminal is false it sends the message again with a delay. A message that waits in the queue costs nothing, and each read is one subrequest.

The Sume key goes in x-api-key. Do not send an Authorization header with it; both together return 401.

export default {
  async queue(batch: MessageBatch<{ jobId: string }>, env: { SUME_API_KEY: string }) {
    for (const msg of batch.messages) {
      const res = await fetch(`https://api.sume.com/v1/jobs/${msg.body.jobId}`, {
        headers: { "x-api-key": env.SUME_API_KEY },
      });
      if (res.status === 429) {
        msg.retry({ delaySeconds: Number(res.headers.get("retry-after") ?? 30) });
        continue;
      }
      const body: any = await res.json();
      const job = body.data ?? body;
      if (job.terminal) msg.ack();
      else msg.retry({ delaySeconds: Math.max(2, job.next_poll_after_seconds ?? 5) });
    }
  },
};

Prefer the webhook when you can

A webhook removes the loop. Sume signs each delivery and verifies with WebCrypto, which runs on Workers. Keep the queue poll as the fallback for a delivery that never arrives, since Sume gives up after 10 attempts.

  • retry-after on a 429 is the wait to honor.
  • A canceled or failed job is terminal too; read status and error.
  • Fetch /result only when result_ready is true.

Why re-enqueue beats a long wait

A consumer that waits for a render holds a Worker invocation. The wall time is a ceiling, not a target, and the invocation has CPU and subrequest budgets as well. A message that is retried with a delay holds nothing while it waits, so the same fleet handles many more jobs. Each retry is one subrequest and a few milliseconds of CPU.

The poll interval comes from Sume. next_poll_after_seconds rises when the queue is long, which spreads reads out without you tuning anything. If you get 429 rate_limited, the retry-after header gives the exact wait.

Poison messages and limits

Put a maximum age on a job id in the message, for example 90 minutes for a video, and stop with an alert after that. A Sume job that is queued for a very long time usually means the workspace is at its plan concurrency, which is 1 on Free, 4 on Pro, 8 on Startup and 20 on Scale. Raise the plan or reduce submissions; polling harder does not help.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume