Advanced

Queue Tasks

Run asynchronous background tasks through VitNode's database-backed queue with retries and AdminCP monitoring.

Queue tasks handle asynchronous, one-off background work (e.g. sending bulk emails, processing media, or scheduling posts). Tasks are persisted in the core_queue table and drained periodically by VitNode's background worker.

Quick start

1. Define a Queue Task

plugins/blog/src/api/tasks/send-newsletter.task.ts
import { buildQueueTask } from "@vitnode/core/api/lib/queue"

export interface NewsletterPayload {
  postId: number
}

export const sendNewsletterTask = buildQueueTask<NewsletterPayload>({
  name: "send-newsletter",
  handler: async (c, payload) => {
    await c.get("log").info(`Processing newsletter for post #${payload.postId}`)
  },
})

2. Register in an API Module

Attach the task to your module's queueTasks list:

plugins/blog/src/api/modules/posts/posts.module.ts
import { buildModule } from "@vitnode/core/api/lib/module"
import { sendNewsletterTask } from "../../tasks/send-newsletter.task"

export const postsModule = buildModule({
  name: "posts",
  routes: [createPostRoute],
  queueTasks: [sendNewsletterTask], 
})

3. Dispatch Tasks

Dispatch jobs from any Hono route or service:

// Dispatch immediately
await c.get("queue").dispatch({
  name: "send-newsletter",
  payload: { postId: 42 },
})

// Or schedule for future execution
await c.get("queue").dispatch({
  name: "send-newsletter",
  payload: { postId: 42 },
  executeAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour from now
  priority: 10, // Higher numbers run first
})

dispatch returns { id } as soon as the task row is written to PostgreSQL.


Retries and Error Handling

VitNode tasks automatically retry on failure using exponential backoff:

export const riskyTask = buildQueueTask({
  name: "sync-external-api",
  maxAttempts: 5, // Default is 3
  handler: async (c, payload) => {
    // If an error is thrown, the task retries automatically
  },
})
AttemptDelay Before Next Retry
1st failure~10 seconds
2nd failure~20 seconds
Each next failureDoubles, capped at 1 hour
Final failureMarked as failed in core_queue

Stale Tasks Are Reclaimed

A task still processing after 15 minutes (QUEUE_STALE_PROCESSING_MINUTES) belonged to a worker that died mid-run. The queue worker puts it back to pending, or marks it failed if it has used every attempt, so a crash never strands a task forever.

Keep handlers well under 15 minutes

A handler that runs longer would be reclaimed and run again while it is still going. Long work should save its progress and dispatch a follow-up task instead - the way notification fan-out does.


AdminCP Queue Monitor

Inspect and debug tasks at Core → Advanced → Queue (/admin/core/advanced/queue):

  • Monitor pending, active, and failed jobs in real time.
  • View failure error traces and retry failed tasks with one click.

buildQueueTask Options

Prop

Type

Learn More