Supabase Queues visibility timeout as a Sume job poller

Supabase Queues (pgmq) hide a read message for a visibility timeout, which fits polling a Sume job: read, check status, archive on terminal, else it reappears.

5 min readSume
All posts

A Supabase Queue read hides the message from other consumers for a visibility timeout, so a message you do not delete or archive comes back on its own. That is exactly the shape of polling a Sume job: enqueue the job id, read it, check GET /v1/jobs/{id}/status, archive it when the job is terminal, and let it reappear otherwise.

The Supabase facts below are from its Queues API page, read on 2026-10-02. It describes Queues as a Postgres-native durable message queue, with the public functions listed in the table.

Which queue functions do you need?

Do not use pop for polling. It deletes on read, so a crash between the read and your status call loses the job id. Use read and delete or archive only after the check.

Supabase Queues public functions, read 2026-10-02
FunctionWhat it does
pgmq_public.sendAdds a message, optionally delayed by a number of seconds
pgmq_public.readReads up to n messages, hiding them for a visibility timeout
pgmq_public.popReads the next message and deletes it
pgmq_public.archiveMoves a message to the queue's archive table
pgmq_public.deleteDeletes a message permanently

How does that map onto a Sume job?

Sume jobs are durable and keep running after your process dies, and the jobs and results docs tell you to store the job id and resume from status_url. A queue row is that stored id with retry built in. If a worker dies mid-check, the message becomes visible again after the timeout and another worker picks it up, and nothing is resubmitted, so no second paid job is created.

What does the consumer loop do?

Each pass reads a few messages, calls the status endpoint for each, and branches on the booleans the endpoint returns:

  • terminal is true and sume_status is completed: fetch GET /v1/jobs/{id}/result, store the artifact URLs, then archive the message.
  • terminal is true and the job failed or was canceled: record the error from GET /v1/jobs/{id}, then archive.
  • terminal is false: leave the message alone. It reappears after the visibility timeout. Set that timeout near the next_poll_after_seconds value Sume returns rather than a fixed guess.
  • The status call returns 429: leave the message and wait for retry-after, per the errors docs.

What should the job record hold?

Put the job_id and the Idempotency-Key you used in the message, along with a count of how many times you have read it. A message that has been read 50 times without reaching a terminal state deserves a human look rather than another round, and the count lets you stop. You can cancel a job with POST /v1/jobs/{id}/cancel only before generation starts; after that Sume returns 409 job_generation_already_started and the job runs on.

Keep results out of the message. Store the artifact URLs in your own table keyed by job_id, and keep the queue for work still to do. That makes the archive table a clean audit of which jobs you have finished checking.

What does this not do?

The queue does not start jobs, and it does not know about Sume's concurrency. Jobs beyond your plan's concurrency sit as queued, which is a normal state, and a full workspace queue answers 429 queue_full at submit. Handle that at the submit step.

It also adds cost of its own: each read is a database operation on your project. For a handful of long video jobs a webhook is simpler. Reach for the queue when you have many jobs, want crash safety, and already run Supabase.

When is a webhook better than this?

A queue poller costs a status read per job per pass. A Sume webhook tells you once, at the terminal event; a receiver is shown in Supabase Edge Function video webhook. The two work well together: the webhook archives the message the moment it arrives, and the queue is the backstop for deliveries that never come after Sume's 10 attempts.

One honest limit: Sume does not push progress, only terminal events, so there is nothing to show between submit and finish except the status you read.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume