nextjs · backend · workers · alternative

Free Self-Hosted Inngest Alternative for Next.js

Escape SaaS step limits and webhook lock-in. A $0 self-hosted background worker engine for Next.js running on your own VPS.

A comparison chart contrasting hosted Inngest step pricing with self-hosted SQLite background workers
A comparison chart contrasting hosted Inngest step pricing with self-hosted SQLite background workers · Photo: BoringPush

Inngest and Trigger.dev are popular tools in the modern Next.js ecosystem.

They solved a real headache: running asynchronous tasks and workflows from serverless environments like Vercel without managing a standalone Redis cluster.

Their developer experience is sleek, their SDKs are clean, and their documentation is polished.

Then your application starts growing, and you run into the realities of hosted background job platforms.

The pricing cliff of hosted workflow services

Hosted background job engines use a business model based on execution steps.

Every time a background task runs a step, delays execution, or retries after a failure, a counter increments on their servers.

When your application is in private beta, their free tier handles your traffic without issue.

Once you launch real features (like automated onboarding emails, daily activity digests, Stripe webhook synchronization, or web scraping routines), your step counts explode.

Suddenly, you exceed the free tier boundary.

The pricing jump is steep. You jump from zero dollars to twenty-five, fifty, or one hundred dollars per month for a single service.

If you run multiple side projects or client applications, those monthly subscriptions compound quickly.

The architectural risk: Webhook tunnel lock-in

Beyond the monthly financial cost, there is an architectural dependency to consider.

Cloud background platforms do not execute your code on their servers. They orchestrate work by sending HTTP webhook requests back into your Next.js application routes (/api/inngest).

This architectural model introduces three distinct points of friction:

First, your application routes must be publicly accessible on the internet for jobs to run. During local development, you must run background tunnels or local gateway emulators.

Second, if the cloud provider experiences an infrastructure outage, their orchestrator stops dispatching webhooks to your server, halting your background processing.

Third, your customer data and execution payloads travel across the public internet to third-party servers, creating compliance and data residency questions.

You are renting external infrastructure to orchestrate code that could run right on your own machine.

The alternative: What self-hosted background workers look like

If you deploy your Next.js application to a virtual private server, you do not need an external cloud orchestrator.

You already have a dedicated CPU, local storage, and a running operating system.

In BoringPush, the self-hosted alternative is boringqueue. It gives you the core capabilities of hosted platforms without the external subscription.

FeatureBoringQueue (Self-Hosted)Hosted Inngest / Trigger.dev
Monthly cost$0 flat on your existing VPS$0 on free tier, then $25 to $100+/mo
Execution limitsUnlimited jobs, bounded only by hardwareBounded by step limits and credit tiers
Network latency0.2ms local disk writeNetwork round-trip over public webhooks
Third-party dependencyZero (completely self-contained)High (outage halts job dispatch)
Local developmentRuns natively with Node (bp jobs work)Requires local Dev Server / Tunnel
InspectionCommand-line CLI (bp jobs list)Browser web dashboard

How to dispatch and handle jobs in BoringPush

The developer experience inside BoringPush mirrors the simplicity of modern workflow tools, without the external dependency.

To dispatch a background task from any Next.js route:

import { dispatchJob } from "@/libs/boringqueue";

export async function POST(req) {
  const { userId, plan } = await req.json();

  // Dispatched locally to SQLite in under 1ms
  dispatchJob("billing/activate", { userId, plan }, {
    maxAttempts: 5,
    runAt: "+1m",
    referenceKey: `activate-${userId}`,
  });

  return Response.json({ status: "processing" });
}

The job logic lives right in your repository under scripts/jobs/billing/activate.mjs:

export default async function (payload, { job, signal }) {
  const { userId, plan } = payload;

  // Real work happens here with your local DB models
  await provisionUserAccount(userId, plan, { signal });

  // Finished cleanly: mark completed
  return { activated: true };
}

The worker process runs under PM2 in the background.

If an API call fails, it retries with automatic exponential backoff. If you dispatch duplicate jobs with the same referenceKey, it ignores duplicates automatically.

When should you pay for Inngest?

Honest architecture acknowledges trade-offs. There are scenarios where paying for a hosted workflow platform is justified:

  1. Complex visual step functions: If your business logic requires twenty sequential steps with conditional human-in-the-loop approvals spanning several weeks, Inngest's step engine is world-class.
  2. Serverless purists: If your entire company is committed to running exclusively on Vercel without ever provisioning a single virtual private server, you must use a hosted queue.

For solo developers, indie hackers, and small teams building SaaS applications, paying for cloud queue steps is an unforced error.

You can own your background processing, run unlimited jobs, and keep your infrastructure bills at zero.

FAQ

Does BoringQueue require a Docker container to run?

No. Unlike self-hosting Inngest or Temporal, which require complex Docker Compose configurations, boringqueue is written in plain Node.js and uses embedded SQLite. It requires zero Docker containers and zero extra dependencies.

How do I view failed jobs without a web dashboard?

BoringPush includes a built-in CLI command: npm run bp -- jobs list --status dead. This prints structured JSON containing the failed job IDs, payloads, and exact error stack traces directly to your terminal.

Can I retry failed jobs after an outage?

Yes. Run npm run bp -- jobs retry-dead. The queue re-enqueues all dead-letter tasks for immediate execution by the worker pool.

How much memory does the background worker use?

The PM2 worker process typically consumes around thirty to fifty megabytes of RAM while idle, scaling up slightly during heavy compute tasks.

What happens if I outgrow a single VPS?

A single $5 Hetzner VPS can comfortably process hundreds of thousands of background jobs per day on SQLite. If you reach the scale where a single server is bottlenecked, your revenue will easily justify migrating to enterprise distributed message brokers.

Keep reading

+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)

The Agent-First Shipping Kit

Command from your phone.

Built so autonomous AI agents build what you want without breaking what's already paying you.

$199

Get BoringPush

One-time payment · Lifetime updates · Unlimited projects.

Keep reading